Skip to main content

Full text of "Mysql PHP"

See other formats


MySQL/ PHP Database 
Applications 



MySQL/ PHP Database 
Applications 

Jay Greenspan and Brad Bulger 



m 



M&T Books 

An imprint of IDG Books Worldwide, Inc. 
Foster City, CA • Chicago, IL • Indianapolis, IN • New York, NY 



MySQL/PHP Database Applications 

Published by 
M&T Books 

An imprint of IDG Books Worldwide, Inc. 

919 E. Hillsdale Blvd., Suite 400 

Foster City, CA 94404 

www. idgbooks . com (IDG Books Worldwide Web site) 

Copyright ©2001 IDG Books Worldwide, Inc. All rights 

reserved. No part of this book, including interior design, 

cover design, and icons, may be reproduced or 

transmitted in any form, by any means (electronic, 

photocopying, recording, or otherwise) without the prior 

written permission of the publisher. 

ISBN: 0-7645-3537-4 

Printed in the United States of America 

10 987654321 

10/QZ/QR/QR/FC 

Distributed in the United States by IDG Books 

Worldwide, Inc. 

Distributed by CDG Books Canada Inc. for Canada; by 

Transworld Publishers Limited in the United Kingdom; by 

IDG Norge Books for Norway; by IDG Sweden Books for 

Sweden; by IDG Books Australia Publishing Corporation 

Pty. Ltd. for Australia and New Zealand; by TransQuest 

Publishers Pte Ltd. for Singapore, Malaysia, Thailand, 

Indonesia, and Hong Kong; by Gotop Information Inc. 

for Taiwan; by ICG Muse, Inc. forjapan; by Intersoft 

or South Africa; by Eyrolles for France; by International 

Thomson Publishing for Germany, Austria, and 

Switzerland; by Distribuidora Cuspide for Argentina; by 

LP, International for Brazil; by Galileo Libros for Chile; by 

Ediciones ZETA S.C.R. Ltda. for Peru; by WS Computer 

Publishing Corporation, Inc., for the Philippines; by 

Contemporanea de Ediciones for Venezuela; by Express 

Computer Distributors for the Caribbean and West Indies; 

by Micronesia Media Distributor, Inc. for Micronesia; by 

Chips Computadoras S.A . de C.V. for M exico; by Editorial 

Norma de Panama S.A. for Panama; by American 

Bookshops for Finland. 

For general information on IDG Books Worldwide's 

books in the U.S., please call our Consumer Customer 



Service department at 800-762-2974. For reseller 

information, including discounts and premium sales, 

please call our Reseller Customer Service department 

at 800-434-3422. 

For information on whereto purchase IDG Books 

Worldwide's books outside the U.S., please contact our 

International Sales department at 317-572-3993 or fax 

317-572-4002. 

For consumer information on foreign language 

translations, please contact our Customer Service 

department at 800-434-3422, fax 317-572-4002, or 

e-mail rights@idgbooks.com. 

For information on licensing foreign or domestic rights, 

please phone +1-650-653-7098. 

For sales inquiries and special prices for bulk quantities, 

please contact our Order Services department at 

800-434-3422 or write to the address above. 

For information on using IDG Books Worldwide's books 

in the classroom or for ordering examination copies, 

please contact our Educational Sales department at 

800-434-2086 or fax 317-572-4005. 

For press review copies, author interviews, or other 

publicity information, please contact our Public Relations 

department at 650-653-7000 or fax 650-653-7500. 

For authorization to photocopy items for corporate, 

personal, or educational use, please contact Copyright 

Clearance Center, 222 Rosewood Drive, Danvers, MA 

01923, or fax 978-750-4470. 

Library of Congress Cataloging- in- Publication Data 

Greenspan, Jay, 1968- 

My SQL/PHP database applications/ Jay Greenspan 
and Brad Bulger, 
p. cm. 
ISBN 0-7645-3537-4 (alk. paper) 
1. SQL (Computer program language) 2. PHP 
(Computer program language 3.Web databases. 
I. Bulger, Brad, 1959- II. Title. 
QA76.73.S67G73 2001 
005.13'3--dc21 00-053995 



LIMIT OF LIABILITY/DISCLAIMER OF WARRANTY : THE PUBLISHER AND AUTHOR HAVE USED THEIR BEST 
EFFORTS IN PREPARING THIS BOOK. THE PUBLISHER AND AUTHOR MAKE NO REPRESENTATIONS OR 
WARRANTIES WITH RESPECT TO THE ACCURACY OR COMPLETENESS OF THE CONTENTS OF THIS BOOK AND 
SPECIFICALLY DISCLAIM ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
PURPOSE. THERE ARE NO WARRANTIES WHICH EXTEND BEYOND THE DESCRIPTIONS CONTAINED IN THIS 
PARAGRAPH. NO WARRANTY MAY BE CREATED OR EXTENDED BY SALES REPRESENTATIVES OR WRITTEN 
SALES MATERIALS. THE ACCURACY AND COMPLETENESS OF THE INFORMATION PROVIDED HEREIN AND THE 
OPINIONS STATED HEREIN ARE NOT GUARANTEED OR WARRANTED TO PRODUCE ANY PARTICULAR RESULTS, 
AND THE ADVICE AND STRATEGIES CONTAINED HEREIN MAY NOT BE SUITABLE FOR EVERY INDIVIDUAL. 
NEITHER THE PUBLISHER NOR AUTHOR SHALL BE LIABLE FOR ANY LOSS OF PROFIT OR ANY OTHER 
COMMERCIAL DAMAGES, INCLUDING BUT NOT LIMITED TO SPECIAL, INCIDENTAL, CONSEQUENTIAL, OR OTHER 
DAMAGES. 



Trademarks: All brand names and product names used in this book are trade names, service marks, trademarks, or 
registered trademarks of their respective owners. IDG Books Worldwide is not associated with any product or vendor 
mentioned in this book. 

_^^_ = is a registered trademark or trademark A ff L t is a trademark of 

ZjF^; under exclusive license to IDG Books Worldwide, Inc. ^li&^K> IDG Books Worldwide, Inc. 

IDtj from International Data Group, Inc. in the 

BOOKS United States and/or other countries. 



WORLDWIDE 




ABOUT IDG BOOKS WORLDWIDE 

Welcome to the world of IDG Books Worldwide. 

IDG Books Worldwide, Inc., is a subsidiary of International DataGroup, the world's largest publisher of 
computer-related information and the leading global provider of information services on information technology. 
IDG was founded more than 30 years ago by Patrick J. McGovern and now employs more than 9,000 people 
worldwide. IDG publishes more than 290 computer publications in over 75 countries. More than 90 million 
people read one or more IDG publications each month. 

Launched in 1990, IDG Books Worldwide is today the #1 publisher of best-selling computer books in the 
U nited States. We are proud to have received eight awards from the Computer Press Association in recognition 
of editorial excellence and three from Computer Currents' First Annual Readers' Choice Awards. Our best- 
selling ...For Dummies* series has more than 50 million copiesin print with translations in 31 languages. IDG 
Books Worldwide, through a joint venture with IDG's Hi-Tech Beijing, became the first U.S. publisher to 
publish a computer book in the People's Republic of China. In record time, IDG Books Worldwide has become 
the first choice for millions of readers around the world who want to learn how to better manage their 
businesses. 

Our mission is simple: Every one of our books is designed to bring extra value and skill-building instructions 
to the reader. Our books are written by experts who understand and care about our readers. The knowledge 
base of our editorial staff comes from years of experience in publishing, education, and journalism — 
experience we use to produce books to carry us into the new millennium. In short, we care about books, so 
we attract the best people. We devote special attention to details such as audience, interior design, use of 
icons, and illustrations. And because we use an efficient process of authoring, editing, and desktop publishing 
our books electronically, we can spend more time ensuring superior content and less time on the technicalities 
of making books. 

You can count on our commitment to deliver high-quality books at competitive prices on topics you want 
to read about. At IDG Books Worldwide, we continue in the IDG tradition of delivering quality for more than 
30 years. You'll find no better book on a subject than one from IDG Books Worldwide. 

i 

■ 




IDG 

BOOKS 

WORLDWIDE 



////JohnKilcullen 



Chairman and CEO 

IDG Books Worldwide, Inc. 




Eighth Annual 
Computer Press 
Awards >^>1992 







Ninth Annual 
Computer I'ress 
Awards >^1993 



Tenth Annual 
Computer Press 
Awards >^1994 



Eleventh Annual 
Computer Press 
Awards ^1995 



IDG is the world's leading IT media, research and exposition company. Founded in 1964, IDG had 1997 revenues of $2.05 
billion and has more than 9,000 employees worldwide. IDG offers the widest range of media options that reach IT buyers 
in 75 countries representing 95% of worldwide IT spending. IDG's diverse product and services portfolio spans six key areas 
including print publishing, online publishing, expositions and conferences, market research, education and training, and 
global marketing services. More than 90 million people read one or more of IDG's 290 magazines and newspapers, including 
IDG's leading global brands— Computerworld, PC World, Network World, Macworld and the Channel World family of 
publications. IDG Books Worldwide is one of the fastest-growing computer book publishers in the world, with more than 
700 titles in 36 languages. The "...For Dummies®" series alone has more than 50 million copiesin print. IDG offers online 
users the largest network of technology-specific Web sites around the world through IDG.net (http://www.idg.net), which 
comprises more than 225 targeted Web sites in 55 countries worldwide. International Data Corporation (IDC) is the world's 
largest provider of information technology data, analysis and consulting, with research centers in over 41 countries and more 
than 400 research analysts worldwide. IDG World Expo is a leading producer of more than 168 globally branded conferences 
and expositions in 35 countries including E3 (Electronic Entertainment Expo), Macworld Expo, ComNet, Windows World 
Expo, ICE (Internet Commerce Expo), Agenda, DEMO, and Spotlight. IDG's training subsidiary, ExecuTrain, isthe world's 
largest computer training company, with more than 230 locations worldwide and 785 training courses. IDG Marketing 
Services helps industry-leading IT companies build international brand recognition by developing global integrated marketing 
programs via IDG 'sprint, online and exposition products worldwide. Further information about the company can be found 
atwww.idg.com. 1/26/00 



Credits 



ACQUISITIONS EDITOR 

Debra Williams Cauley 

PROJ ECT EDITOR 

Neil Romanosky 

TECHNICAL EDITORS 

Richard Lynch 
Michael Widenius 

COPY EDITOR 

S. B. Kleinman 

PROJ ECT COORDINATORS 

LouigeneA. Santos 
Danette Nurse 

GRAPHICS AND PRODUCTION 
SPECIALISTS 

Robert Bilhmayer 
Roily Delrosario 
J ude Levinson 
Michael Lewis 
Ramses Ramirez 
Victor Perez-Varela 



QUALITY CONTROL TECHNICIAN 

Dina F Quan 

PERMISSIONS EDITOR 

Laura Moss 

MEDIA DEVELOPMENT SPECIALIST 

Angela Denny 

MEDIA DEVELOPMENT COORDINATOR 

Marisa Pearman 

BOOK DESIGNER 

Jim Donohue 

ILLUSTRATORS 

Gabriele McCann 
Ronald Terry 

PROOFREADING AND INDEXING 

York Production Services 

COVER IMAGE 

©Noma/lmages.com 



About the Authors 



Jay Greenspan made his living as a technical consultant and editor before finding 
his way into Wired Digital's Webmonkey. There he learned everything he knows 
about Web technology and gained an appreciation for electronic music, the color 
orange, and a "cute top." He now makes his living as a writer and consultant. He 
will neither confirm nor deny the rumors that he once worked for a prime-time 
game show. 

Brad Bulger can remember when computers were as big as refrigerators and old- 
timers would come into the machine room and call them "mini." He learned more 
than anyone really should about database systems by working for Relational 
Technology nee Ingres nee CA for many years. After an interregnum, he got a job 
with Wired. He would still like to know when the future is going to get here, but has 
a sneaking suspicion he already knows. 



In memory of Dr. Jonathan B. Postel 



Preface 



Welcome. If you are thumbing through these pages, you're probably considering 
writing Web-based applications with PHP and MySQL. If you decide to go with 
these tools, you'll be in excellent company. Thousands of developers— from total 
newbies to programmers with years of experience— are turning to PHP and MySQL 
for their Web- based projects; and for good reason. 

Both PHP and MySQL are easy to use, fast, free, and powerful. If you want to get 
a dynamic Web site up quickly, there are no better choices. The PHP scripting lan- 
guage was built for the Web. All the tasks common to Web development can be 
accomplished in PHP with an absolute minimum of effort. Similarly, MySQL excels at 
tasks common to dynamic Web sites. Whether you're creating a content- management 
system or an e-commerce application, MySQL is a great choice for your data storage. 



Is This Book for You? 



There are quite a few books that deal with PHP and a few that cover MySQL. We've 
read some of these and found a few to be quite helpful. If you're looking for a book 
that deals with gory details of either of these packages, you should probably look 
elsewhere. 

The focus of this book is applications development. We are concerned with what 
it takes to get data-driven Web sites up and running in an organized and efficient 
way. The book does not go into arcane detail of every aspect of either of these tools. 
For example, in this book, you will not find a discussion of PHP's LDAP functions 
or MySQL's C application program interface (API). Instead, we will focus on the 
pieces of both packages that affect one another. We hope that by the time you're 
done with this book you'll know what it takes to get an application up and running 
using PHP and MySQL. 



How This Book Is Organized 

We have organized the book into four parts. 



Part I: Using MySQL 



Before you code any PHP scripts, you will need to know how to design a database, 
create tables in your database, and get the information you want from the database. 
Part I of this book will show you about all you need to know to work with MySQL. 



IX 



Preface 



Part II: Using PHP 



As an applications developer, the bulk of your time will be spent writing scripts that 
access the database and present HTML to a user's browser. Part II will start by 
showing you the basics of the PHP scripting language, covering how PHP works 
with variables, conditions, and control structures. Part II will also cover many of 
PHP's functions and discuss techniques for writing clean, manageable code. 

Part III: Simple Applications 

In this part, we present two of the seven applications in this book: a guestbook and 
a survey. Here you will see the lessons from Parts I and II put into practice as we 
build working applications. 

Part IV: Not So Simple Applications 

Here the applications will be more complex, as we present applications commonly 
used on the Web. You will see how you can design a content management system, 
a discussion board, a shopping cart, and other useful applications. Along the way, 
we will show some tips and techniques that should be helpful as you write your 
applications. 



Part V: Appendixes 



The appendixes cover several topics of interest to the MySQL/PHP developer. In the 
appendixes, you will find installation and configuration instructions, quick refer- 
ence guides to PHP and MySQL functions, a regular expressions overview, and 
guides to MySQL administration. In addition, there are a few helpful resources, 
snippets of code, and instructions on using the CD-ROM . 



Tell Us What You Think 

Both the publisher and authors of this book hope you find it a valuable resource. 
Please feel free to register this book at the IDG Books Web site (http://www. 
idgbooks.com) and give us your feedback. Also check in at the site we've dedicated 
to this book, http://www.mysq! phpapps.com/, where you will be able to contact 
the authors and find updates to the applications created for this book. 



Acknowledgments 



This book would never have happened if not for the efforts of Debra Williams 
Cauley. I thank her for her patience and persistence. The efforts and talents of Neil 
Romanosky, S. B. Kleinman, and many others at IDG Books have made this book 
more lucid and attractive than we could have hoped. Richard Lynch's exacting eye 
and technical acumen kept our code clean, fast, and readable. 

Any book on open-source software owes debt to those who have created these 
great tools. So I thank everyone involved with PHP and MySQL, from the core 
developers to those who contribute to the documentation. Special thanks to 
Michael (Monty) Widenius, MySQL's lead developer. He has not only created a ter- 
rific relational database, but has offered his advice and expertise to the authors of 
this book. 



XI 



Contents at a Glance 



Preface ix 

Acknowledgments xi 

Introduction xxiii 

Part I Working with MySQL 

Chapter 1 Database Design with MySQL 3 

Chapter 2 The Structured Query Language for Creating and 

Altering Tables 21 

Chapter 3 Getting What You Want with select 45 

Part II Working with PHP 

Chapter 4 Getting Started with PHP— Variables 71 

Chapter 5 Control Structures 95 

Chapter 6 PHP's Built-in Functions Ill 

Chapter 7 Writing Organized and Readable Code 165 

Part III Simple Applications 

Chapter 8 Guestbook 2000, 

the (Semi-)Bulletproof Guestbook 193 

Chapter 9 Survey 215 

Part IV Not So Simple Applications 

Chapter 10 Catalog 249 

Chapter 11 Content Management System 285 

Chapter 12 Threaded Discussion 311 

Chapter 13 Problem Tracking System 331 

Chapter 14 Shopping Cart 361 



PartV 



Appendixes 



Appendix A 
Appendix B 

Appendix C 
Appendix D 
Appendix E 
Appendix F 
Appendix G 
Appendix H 
Appendix I 
Appendix J 



HTML Forms 405 

Brief Guide to PHP/MySQL Installation and 

Configuration 413 

MySQL Utilities 423 

MySQL User Administration 439 

PHP Function Reference 447 

Regular Expressions Overview 507 

Helpful User- Defined Functions 517 

PHP and MySQL Resources 543 

MySQL Function Reference 551 

What's on the CD-ROM 585 

Index 587 

End- User License Agreement 599 

GNU General Public License 602 

CD-ROM Installation Instructions 608 



Contents 



Preface ix 

Acknowledgments xi 

Introduction xxiii 

Part I Working with MySQL 

Chapter 1 Database Design with MySQL 3 

Why Use a Relational Database? 3 

Blasted Anomalies 5 

Update anomaly 5 

Delete anomaly 8 

Insert anomaly 10 

Normalization 10 

1st normal form 10 

2nd normal form 12 

3rd normal form 13 

Types of Relationships 13 

One- to-many relationship 14 

One-to-one relationship 14 

Many-to-many relationship 16 

Features MySQL Does Not Support 17 

Referential integrity 17 

Transactions 18 

Stored procedures 19 

Chapter 2 The Structured Query Language for Creating and 

Altering Tables 21 

Definitions 22 

Null 22 

Index 24 

create database Statement 24 

use database Statement 25 

create table Statement 26 

Column Types 27 

Text column types 27 

Numeric column types 30 

Date and time types 31 

Creating Indexes 33 



XVI 



Contents 



Table Types 35 

alter table Statement 35 

Changing a table name 36 

Adding and dropping columns 36 

Adding and dropping indexes 37 

Changing column definitions 37 

insert Statement 38 

update Statement 39 

drop table/drop database 40 

show tables 40 

show columns/show fields 41 

Using phpMyAdmin 43 

Chapter 3 Getting What You Want with select 45 

Basic select 45 

The where clause 48 

order by 53 

limit 54 

group by and aggregate functions 54 

having 60 

J oining Tables 61 

Two- table join (the equi-join) 61 

Multi-tablejoin 63 

outer join 64 

self join 65 

Portions of SQL the SQL Standard that MySQL Doesn't 

Support 67 

Unions 67 

Correlated subqueries 67 

Working with PHP 

Chapter 4 Getting Started with PHP— Variables 71 

Assigning Simple Variables Within a Script 71 

Delimiting Strings 72 

Assigning arrays within a script 74 

Assigning two-dimensional arrays in a script 76 

Accessing Variables Passed from the Browser 77 

HTM L forms variables 77 

Passing arrays 79 

Cookies 81 

Sessions 83 

Using Built-in Variables 85 

PHP variables 86 

Apache variables 87 



Part II 



Contents 



XVII 



Other Web server variables 89 

Testing Variables 90 

isset() 90 

empty!) 91 

is_int() 91 

is_double() 91 

is_string() 91 

is_array() 92 

is_bool() 92 

is_object() 92 

gettypeO 92 

Changing Variable Types 92 

Type casting 93 

Using settypeO 93 

intvalO, doublevalO, and stringvalO 93 

Variable Variables 93 

Chapter 5 Control Structures 95 

The if Statements 95 

Determining true or false in PHP 96 

Comparison operators 98 

Logical operators 98 

Complex if statements 99 

if ... else statements 100 

if ... elseif statements 100 

Alternative if... structures 101 

switch ... case 102 

Loops 103 

while 103 

do ...while 106 

for 106 

foreach 107 

continue and break 108 

continue 108 

break 108 

Including files 109 

Chapter 6 PHP's Built-in Functions ill 

Function Basics 112 

Arguments 112 

Return values 113 

Function Documentation 114 

Important PHP 4 Functions 114 

MySQL API 115 

String-handling functions 125 

Regular expression functions 135 



XVII 



Contents 



Type- conversion functions 140 

Array functions 143 

Print functions 148 

Date/time functions 150 

Filesystem functions 154 

Random number generator functions 157 

cURL functions 158 

Session functions 158 

HTTP header functions 158 

Mail function 160 

URL functions 161 

Output buffering 162 

Chapter 7 Writing Organized and Readable Code 165 

Indenting 165 

Code blocks 166 

Function calls 167 

SQL statements 168 

Includes 169 

included and required 171 

include_once() and require_once() 171 

User-Defined Functions 172 

Function basics 173 

Returning values 175 

Using a variable number of arguments 177 

Variable scope 178 

Object-Oriented Programming 180 

Classes 181 

Instantiating an object 184 

Inheritance 185 

Object-Oriented Code versus Procedural Code 187 

Comments 187 



Part III Simple Applications 

Chapter 8 Guestbook 2000, 

the (Semi-)Bulletproof Guestbook 193 

Determining the Scope and Goals of the Application 193 

Necessary Pages 194 

What do we need to prevent? 195 

Designing the Database 199 

Code Overview 200 

Code Breakdown 201 

Reusable functions 201 

Interesting code flow 211 

Scripts 214 



Contents 



XIX 



Chapter 9 Survey 215 

Determining the Scope and Goals of the Application 215 

Necessary Pages 216 

What do we need to prevent? 219 

Designing the Database 220 

Code Overview 224 

Code Breakdown 225 

Reusable functions 225 

Interesting Code Flow 238 

admin_question.php 238 

admin_get_winner.php 242 

admin_winners.php 243 

Part IV Not So Simple Applications 

Chapter 10 Catalog 249 

Determining the Scope and Goals of the Application 250 

Necessary Pages 251 

What Do We Need to Prevent? 255 

The Data 255 

A flawed data design 255 

MySQL oddities 257 

A better schema 259 

Code Overview 262 

The object-oriented approach 262 

Accessing the filesystem 263 

Uploading files 263 

Accessing outside utilities 264 

Code Breakdown 265 

Objects in theory 266 

Objects in practice 267 

Classes 268 

Sample Script 282 

Chapter 11 Content Management System 285 

Determining the Scope and Goals of the Application 286 

Necessary pages 286 

What do you need to prevent? 291 

Designing the Database 291 

Code Overview 298 

Code Breakdown 298 

Reusable functions 298 

Interesting Code Flow 301 

content/authenticate.php 301 

content/admin_user.php 304 

content/edit_story.php 308 



XIX 



XX 



Contents 
Chapter 12 



Chapter 13 



Chapter 14 



Threaded Discussion 311 

Determining the Scope and Goals of the Application 312 

What do you need? 312 

What do you need to prevent? 315 

The Data 316 

Code Overview 320 

Code Breakdown 321 

Reusable functions 321 

Other Files 330 

index. php 330 

Problem Tracking System 331 

Determining the Scope and Goals of the Application 331 

What do you need? 332 

What do you need to prevent? 335 

Designing the Database 335 

Code Overview 340 

Code Breakdown 340 

Reusable functions 340 

Scripts 354 

Shopping Cart 361 

Determining the Scope and Goals of the Application 361 

What do you need? 363 

What do you need to prevent? 365 

The Data 366 

Configuration Overview 369 

Configuring for encryption and security 369 

Encryption and security tools 371 

Configuring for credit-card authorization 372 

Configuring for session handling 372 

Code Overview 373 

Session functions 374 

cURL functions 376 

Dealing with the credit-card processor 377 

Code Breakdown 378 

Classes 380 

Scripts 387 



Contents 



XXI 



PartV 



Appendixes 



Appendix A 
Appendix B 

Appendix C 
Appendix D 
Appendix E 
Appendix F 
Appendix G 
Appendix H 
Appendix I 
Appendix J 



HTML Forms 405 

Brief Guide to PHP/MySQL Installation and 

Configuration 413 

MySQL Utilities 423 

MySQL User Administration 439 

PHP Function Reference 447 

Regular Expressions Overview 507 

Helpful User- Defined Functions 517 

PHP and MySQL Resources 543 

MySQL Function Reference 551 

What's on the CD-ROM 585 

Index 587 

End- User License Agreement 599 

GNU General Public License 602 

CD-ROM Installation Instructions 608 



Introduction 



Soon we will head off on a fabulous journey, a journey on which we will explore 
the ins and outs of MySQL and PHP database applications in great detail. It's going 
to be a fun trip; we just know it. 

OK, maybe we're being a bit optimistic. If you're anything like us, there will be 
points when this particular journey will be a lot more tedious than it is exciting. 
Let's face facts: application development isn't always the most exciting thing in the 
world. And as with any other venture that involves programming, there are sure to 
be some very frustrating times, whether because of a syntax error you can't find or 
a piece of code that won't do what you think it ought to do. But despite all that, 
here you are, and I think there is a very good reason for your being here. 

Web applications are the present and the future. No matter your background, whether 
it be Visual Basic or COBOL, or maybe you know just some HTML and JavaScript, your 
resume is only going to improve with some Web applications development experience. 
We don't think there's a better combination of tools to have under your belt than PHP 
and MySQL. The numbers bear us out. PHP and MySQL are becoming increasingly pop- 
ular, and the demand for people who can use these tools will only increase. 

But a bit later there will be more details on why you should use PHP and MySQL. 
Before we can get into the details of that, we want take a bit of time to go over the 
architecture of Web applications. Once we've done this, we will be able to explain 
in detail why PHP and MySQL should be the centerpieces of your application devel- 
opment environment. Once we've sold you on these tools, we'll present a very quick 
and grossly under-coded application. As you look over this application, you will 
seethe basic syntax and principles behind PHP and MySQL. 




As we proceed with the book, we will assume that you have read and under- 
stand everything presented in this introduction. 



Basic Architecture 



At the most basic level, the Web works off of a client/server architecture. Simply 
stated, that means that both a central server and a client application are responsi- 
ble for some amount of processing. This differs from a program such as Microsoft 
Word, which operates just fine without any help from a server. Those of you who 
used older VAX machines will remember the days of dumb terminals, which had no 
processing power whatsoever. Depending on where you work today, perhaps in a 
university or a bank, you may still use applications that are in no way dependent 
on the client. In other words, all the work is done on the central computer. 



xxiv Introduction 



The client 



The applications you can develop with MySQL and PHP make use of a single client: 
the Web browser. This is not the only possibility for Internet- based applications. 
For very sophisticated applications that require more client-side processing or that 
need to maintain state (we will talk about maintaining state later in the Introduc- 
tion), a Java applet may be necessary. But unless you're coding something like a 
real-time chat program, client-side Java is completely unnecessary. 

So the only client you should be concerned with is the Web browser. The appli- 
cations will need to render in the browser. As you probably already know, the pri- 
mary language of browsers is the hypertext markup language or HTML. HTML 
provides a set of tags that describe how a Web page should look. If you are new to 
the concept of HTM L, get on the Web and read one of the many tutorials out there. 
It shouldn't take that much time to learn the basics. 

Of course, most browsers will accept more than HTML. There are all kinds of 
plug-ins, including RealPlayer, Flash, and Shockwave. Most browsers also have 
some level of support for J avaScript, and some of the newer ones can work with 
XML. But, like most Web developers, we will betaking a lowest-common-denomi- 
nator approach in this book. We're going to create applications that can be read in 
any browser. There will be no JavaScript, XML, or anything else that could prevent 
some users from rendering the pages we serve. HTM L it is. 

The server 

Almost all of the work of Web applications takes place on the server. A specific appli- 
cation, called a Web server, will be responsible for communicating with the browser. 
A relational database server stores whatever information the application requires. 
Finally, you need a language to broker requests between the Web server and the data- 
base server; it will also be used to perform programmatic tasks on the information 
that comes to and from the Web server. Figure 1-1 represents this system. 

But of course none of this is possible without an operating system. The Web 
server, programming language, and database server you use must work well with 
your operating system. 

OPERATING SYSTEM 

There are many operating systems out there. Windows 98 and Macintosh OS are 
probably the most popular. But that's hardly the end of it. Circumstances may have 
forced you to work with some obscure OS for the past few years. You may even be 
under the impression that your OS is the best thing going. That's fine. But if you're 
planning on spending a lot of time on the Web and are planning on running appli- 
cations, you're best off getting to know either Windows NT/2000 or Unix. These two 
account for well over 90 percent of all the Web servers on the Web. It is probably 
easier for you to learn a little NT/2000 or Unix than it is to convince everybody else 
that the AS/400 is the way to go. 



Introduction 



xxv 



Web Server 

(Apache.IIS) 



Relational 
Database 

(MySQL, Oracle, MS 
SQL) 



"I 



Middleware 

PHP, ColdFusion, 
ASP.JSP 



i 
T 


i 






Internet 







Web Browser 

(Internet Explorer, 
Netscape) 



Figure I- 1: Architecture of Web applications 



Which should you use? Well, this is a complex question, and the answer for 
many will be based partially on religion. In case you're unaware of it, let's take a 
moment to talk about the broad topics in this religious war. 

If you don't know what we are talking about, here are the basics. PHP and 
MySQL belong to a class of software known as open source. This means that the 
source code to the heart of their applications is available to anyone who wants to 
see it. They make use of an open-source development model, which allows anyone 
who is interested to participate in the development of the project. In the case of 
PHP, coders all over the world participate in the development of the language and 
see no immediate pay for their substantial work. Most of the people who participate 
are passionate about good software and code for the enjoyment of seeing people 
like you and me develop with their tools. 

This method of development has been around for some time, but it has gained 
prominence as Linux has become increasingly popular. More often than not, open- 
source software is free. You can download the application, install it, and use it 
without getting permission from anyone or paying a dime to anyone. 

Suffice it to say that M icrosoft, Oracle, and other traditional software companies 
do not make use of this method of development. 

If you are not an open-source zealot, there are excellent reasons to choose 
NT/2000. Usually, the thing that steers people towards NT/2000 is inertia. If you or 
your company has been developing with M icrosoft products for years, it is probably 
going to be easier to stay within that environment. If you have a team of people who 



xxvi Introduction 



know Visual Basic, you are probably going to want to stick with NT/2000. Even if 
this is the case, there's nothing to prevent you from developing with PHP and 
MySQL. Both products run on Windows 95/98 and Windows NT/2000. 

But in the real world, almost all PHP/MySQL applications are running off of 
some version of Unix, whether it be Linux, BSD, Irix, Solaris, HP-UX, or one of the 
other flavors. For that reason, the applications in this book will work with Unix. If 
you need to run these on Windows, minor alterations to the PHP scripts may be 
necessary. Most of the people who created PHP and MySQL are deeply involved 
with Unix, and most of their development is done on Unix machines, so it's not 
surprising that the software they have created works best on Linux, BSD, and other 
Unix boxes. 

The major advantage of Unix is its inherent stability. Boxes loaded with Linux 
have been known to run months or years without crashing. Linux and BSD also 
have the advantage of being free and able to run on standard PC hardware. If you 
have any old 486, you can load it up with Linux, MySQL, PHP, and Apache and 
have yourself a well -outfitted Web server. You probably wouldn't want to put this 
on the Web, where a moderate amount of traffic might overwhelm it, but it will 
serve nicely as a development server, a place where you can test your applications. 

WEB SERVER 

The Web server has what seems to be a fairly straightforward job. It sits there, run- 
ning on top of your operating system, listening for requests that somebody on the 
Web might make, responds to those requests, and serves out the appropriate Web 
pages. In reality, it is a bit more complicated than that, and because of the 24/7 
nature of the Web, stability of the Web server is a major issue. 

There are many Web servers out there, but two Web servers dominate the mar- 
ket. They are Apache and Microsoft's Internet Information Server (IIS). 

INTERNET INFORMATION SERVER IIS is deeply tied to the Windows environment 
and is a key component of M icrosoft's Active Server Pages. If you've chosen to go 
the Microsoft way, you'll almost certainly end up using IIS. 

There is a certain amount of integration between the programming language and 
Web server. At this point, PHP 4 integrates well with IIS. As of this writing, there is 
some concern about the stability of PHP/IIS under heavy load, but PHP is improv- 
ing all the time, and by the time you read this there may no longer be a problem. 

APACHE The Apache Web server is the most popular Web server there is. It, like 
Linux, PHP, and MySQL, is an open-source project. Not surprisingly, Apache works 
best in Unix environments, but also runs just fine under Windows. 

Apache makes use of third-party modules. Because it is open source, anyone 
with the skill can write code that extends the functionality of Apache. PHP will 
most often run as an Apache extension, known as an Apache module. 

Apache is a great Web server. It is extremely quick and amazingly stable. The 
most frequently stated complaint about Apache is that, like many pieces of Unix 
software, there are limited graphical tools with which you can manipulate the 



Introduction xxvii 



application. You alter Apache by specifying options on the command line or by 
altering text files. When you come to Apache for the first time, all this can be a bit 
opaque. 

Though Apache works best on Unix systems, there are also versions that run on 
Windows operating systems. Nobody, not even the Apache developers, recommends 
that Apache be run on a busy server under Windows. If you have decided to use the 
Windows platform for serving Web pages, you're better off using IIS. 

But there are conditions under which you'll be glad Apache does run under 
Windows. You can run Apache, PHP, and MySQL on a Windows 98 machine and 
then transfer those applications to Linux with practically no changes to the scripts. 
This is the easiest way to go if you need to develop locally on Windows but to serve 
off a Unix/Apache server. 

MIDDLEWARE 

PHP belongs to a class of languages known as middleware. These languages work 
closely with the Web server to interpret the requests made from the World Wide 
Web, process these requests, interact with other programs on the server to fulfill the 
requests, and then indicate to the Web server exactly what to serve to the client's 
browser. 

The middleware is where you'll be doing the vast majority of your work. With a 
little luck, you can have your Web server up and running without a whole lot of 
effort. And once it is up and running, you won't need to fool with it a whole lot. 

But as you are developing your applications, you'll spend a lot of time writing 
code that makes your applications work. In addition to PHP, there are several lan- 
guages that perform similar functions. Some of the more popular choices are ASP, 
Perl, and ColdFusion. 

RELATIONAL DATABASES 

Relational Database Management Systems (RDBMSs) provide a great way to store 
and access complex information. They have been around for quite a while. In fact, 
they predate the Web, Linux, and Windows NT, so it should be no surprise that 
there are many RDBMSs to choose from. All of the major databases make use of the 
Structured Query Language (SQL). 

Some of the more popular commercial RDBMSs are Oracle, Sybase, Informix, 
Microsoft's SQL Server, and IBM's db2. In addition to MySQL, there are now two 
major open-source relational databases. Postgres has been the major alternative to 
MySQL in the open- source arena for sometime. In August 1999, Borland released its 
Interbase product under an open-source license and allowed free download and use. 



Why these Products? 



Given the number of choices out there, you may be asking yourself why you should 
choose PHP and/or MySQL. We will answer this question in the following three 
sections. 



xxviii Introduction 



Why PHP? 



Programming languages are a lot like shoes. Some look good to some people yet 
look really ugly to others. To carry the analogy a little further, some shoes just fit 
well on some feet. 

What we mean is this: when it comes to Web programming, all languages do 
pretty much the same thing: They all interact with relational databases; they all 
work with a filesystem; they all interact with a Web server. The question about 
which language is best is rarely a matter of a language's inability to perform cer- 
tain actions. It's usually more a matter of how quickly you can do what you need 
to do with the least amount of pain. 

IT'S FAST AND EASY 

What about speed? There are really only three things that we know for sure when it 
comes to comparing speeds of Web programming languages. First, applications 
written in C will be the fastest. Second, programming in C is rather difficult and 
will take much longer than any of the other languages mentioned so far. Third, 
comparisons between languages are extremely difficult. From everything we know, 
we feel safe in saying the PHP is as fast as anything out there. 

More often than not choosing a language comes back to the same issues 
involved in buying shoes. You'll want to go with what's most comfortable. If you're 
like us, you will find that PHP has managed the perfect mix of power, structure, and 
ease of use. Again, this is largely a matter of opinion, but we do believe the syntax 
of PHP is superior to that of ASP and J SP. And we believe it puts more power at 
your fingertips more quickly than ColdFusion and is not as difficult to learn as Perl. 

In the end, we believe PHP offers the best opportunity to develop powerful Web 
applications quickly. That generalization made, we do believe there are other excel- 
lent reasons for choosing PHP. 

IT'S CROSS- PLATFORM 

In the rundown of Web architecture, we mentioned that PHP will run on Windows 
2000/NTand Unix and with both IIS and Apache. But the cross- platform abilities of 
PHP go far beyond these platforms. If you happen to be using Netscape, Roxen, or 
just about anything else, it is likely PHP will work with it. 

Yes, ASP can be run on Linux, and ColdFusion can work on Solaris and Linux, 
and J SP is adaptable across many platforms. At this point, PHP works as well on as 
wide a variety of systems as any other available product. 

IT ACCESSES EVERYTHING 

What do you need to access in the course of creating your Web applications? 
LDAP? IMAP mail server? Oracle? Informix? DB2? Or maybe you need an XML 
parser or WDDX functions. 

Whatever you need to use, it is more than likely that PHP has a built-in set of 
functions that make getting whatever you need very easy. But what if it doesn't 
have something built in that you'd like? That brings us to our next point. 



Introduction xxix 



IT'S CONSTANTLY BEING IMPROVED 

If you are new to open source development, you might be surprised by the high 
quality of the software. There are thousands of very technical, very talented pro- 
grammers out there who love to spend their time creating great, and mostly free, 
software. In an active project such as PHP, there is a variety of developers looking 
to improve the product almost daily. 

It is truly remarkable. If you happen to find a bug, you can submit a report to a 
mailing list that the core developers read. Depending on its severity, it is likely that 
the bug will be addressed within a couple of hours to a couple of days. 

When PHP 4 was put together, it was done so in a modular fashion. This makes 
adding greater functionality reasonably easy. If there are sets of functions you'd 
like added to PHP, there's a good chance that someone will be able to do it with 
minimal effort. 

YOUR PEERS WILL SUPPORT YOU 

Most languages have active mailing lists and development sites. PHP is no excep- 
tion. If you run into trouble— if there's a bug in your code you just can't figure out 
or you can't seem to fathom some function or another— someone among the hun- 
dreds subscribed to PHP mailing lists will be happy to check and fix your code. 

The open-source nature of PHP creates a real feeling of community. When you 
get into trouble, your PHP-hacking brethren will feel your pain and ease it. 

IT'S FREE 

If you have a computer, Linux, Apache, and PHP are all completely free. 



Why MySQL? 



This one is perhaps a little tougher to answer. Although MySQL has much to rec- 
ommend it, it also has a variety of competitors, many of whom may be better suited 
for a particular task. 

In Parti of this book, MySQL is discussed in some detail. In these chapters, you'll 
see that we mention features available in other relational databases that MySQL 
does not support. (If you know your way around databases and are curious, these 
include stored procedures, triggers, referential integrity, and SQL unions and sub- 
queries.) Given these limitations, there are definitely environments where MySQL 
would not be the best choice. If you are planning on starting, say, a bank (you 
know, a savings and loan), MySQL probably isn't for you. 

But for the majority of people in the majority of applications, MySQL is a great 
choice. It is particularly well suited for Web applications. 

IT'S COST- EFFECTIVE 

Think you need an Oracle installation? Get ready to shell out somewhere between 
$30,000-$100,000 or more. There's no doubt that Oracle, Sybase, and Informix cre- 
ate terrific databases, but the cost involved will be prohibitive for many. 
MySQL is free. You can install and use it and pay nothing in the process. 



xxx Introduction 



IT'S QUICK AND POWERFUL 

MySQL may not have every bell and whistle available for a relational database, but 
for most users there is plenty. If you are serving out Web content or creating a 
moderately sized commerce site, MySQL has all the power you need. 

For small-to-medium-sized databases, MySQL will be extremely fast. The devel- 
opers of MySQL take great pride in the speed of their product. For applications like 
the ones presented in Parts III and IV of this book, it is unlikely you'll find a data- 
base that's any faster. 

IT'S IMPROVING ALL THE TIME 

MySQL is improving at a staggering rate. The developers release updates frequently 
and are adding impressive (and we do mean impressive) features all the time. 
Recently, MySQL added support for transactions; they are apparently at work now 
on stored procedures. 



MySQL transaction support was added shortly before this writing.Therefore, 
applications in this bookthat might make use of transactions do not. 



All in all, MySQL is an excellent product and getting better all the time. 




Your First Application 



Enough of the prelude. Let's get to writing an application so you can see how all of 
these parts come together in a real live application. By the time you have finished 
reading this intra, you should have a pretty good idea of how it all comes together. 

Tool check 

There are a few key elements you need to get going. We'll run through them here so 
you'll know what you need. 

SOFTWARE 

This is a Web- based application, so you're clearly going to need a Web server. You 
will probably be using Apache, whether you are using Windows or Unix. You will 
need to install Apache so that it can access the PHP language. 

In addition, you will need to have MySQL installed. And PHP will have to be 
able to recognize MySQL. Apache, MySQL, and PHP are provided on the accompa- 
nying CD, and installation instructions are provided in Appendix B. You may want 
to install these packages before proceeding, or you could just read along to get an 
idea of what we're doing and install the packages later when you want to work with 
the more practical examples in this book. 



Introduction xxxi 



TEXT EDITOR 

As of this writing, there are no slick, integrated development environments (IDEs) 
for PHP. To code PHP and your Web pages, you will need a text editor. You could use 
Notepad or something similarly basic, but if you're starting without an allegiance to 
any particular editor, I suggest you get something with good syntax highlighting. On 
Windows, Allaire's Homesite (www.al lai re.com) is a tool that works well with PHP, 
and we've heard excellent things about Editplus (www.editpi us .com). 

If you have been working on Unix for some time, it is likely that you already know 
and love some text editor or another, whether it be Emacs, vi , or Kedit. If not, any of 
these are fine, though the first two do take some getting used to. If you're woking on 
Unix, but don't have the patience to learn vi, try Pico. It's very easy to use. 



If you need a text editor under Unix but don't know your way around vi, try 
Pico. It's a very basic, easy-to-use text editor. 




Application overview 



We thought we would start this book with something really exotic, a Web applica- 
tion that's mind-blowingly original, something never before seen on the Web. After 
a great brainstorming session, when we contacted some of the brightest people on 
the Web, and geniuses in other creative fields, we found the perfect thing. We'd 
write an application that stores user information, a place where users can enter 
their names, e-mail addresses, URLs, and maybe even comments. After lengthy dis- 
cussion, and deep prayer, we decided on a name for this application. It is now and 
forever to be known as a guestbook. 



The guestbook is a simplified example, something you would never want to 
run on a live Web server. We re-create this application in a more robust form 
in Chapter 8. 



Create the database 

Now that you know exactly what you need , the first step is to create a database 
that will store this information. To do this, you will use the language common to 
most every database server: the Structured Query Language (SQL). You will read a 
lot more about this later, so don't worry if you don't understand everything right 
away. J ust read through the rest of the Introduction and then read Chapter 1. 

Start up the MySQL command-line client. If you're working on Unix, typing 
mysql at the shell should do the trick (or you might have to go to the /mysql/bin 
directory). If you are on Windows, you will need to go to the DOS prompt, find the 




xxxii Introduction 



path to mysql.exe, and execute it. Then, at the prompt, create a new database. 
When you're done, you should have something that looks very much like this: 

[jay@mybox jay]$ mysql 

Welcome to the MySQL monitor. Commands end with ; or \g. 

Your MySQL connection id is 716 to server version: 3.22.27-log 

Type 'help' for help. 

mysql> create database guestbook; 
Query OK, 1 row affected (0.00 sec) 

mysql > 

Now, within the database named guestbook, you will need a table that stores the 
user information. This table is also created in the MySQL monitor. The command to 
create the table isn't very complex. You basically need to let MySQL know what 
kind of information to expect, whether numbers or stings, and whether or not any 
of the information can be omitted (or NULL). The basic command is create table; it 
will look about like this when you make the table: 

mysql> use guestbook 

Database changed 

mysql> create table guestbook 

-> ( 

-> name varchar(40) null, 

-> location varchar(40) null, 

-> email varchar(40) null, 

-> url varchar(40) null, 

-> comments text null 



Query OK, rows affected (0.00 sec) 
mysql > 

So now you have a database named guestbook and a table within the database 
named guestbook. Now it's time to write an application in PHP that will enable you 
to insert, edit, and view information kept in this guestbook. 



Your PHP Script 



Now's the time to move to the text editor. In the course of configuring your Web 
server, you will need to let it know which files should be handed off to PHP so the 
engine can interpret the page. Most often, these files will have a .php extension, 
though it is possible to have PHP interpret anything, including .html files. These 



Introduction xxxiii 



scripts will live inside the folder designated to hold Web pages. For Apache, this 
will usually be/htdocs. 

BASIC SYNTAX 

One neat thing about PHP is that it lets you move between straight HTML and com- 
mands that are part of the PHP programming language. It works like this: The sections 
of your script between the opening tag <?php and a closing tag ?> will be interpreted 
by the PHP engine, and portions not within these tags will be treated as plain HTML. 
Check out the following PHP page. 

<?php 

echo "Hi,"; 

?> 

mom. 

When run through the Web server, this would create a Web page that prints, sim- 
ply, "Hi, mom." PHP's echo command manages the first part of the line. But, of 
course, PHP can do quite a bit more than that. Like any other programming lan- 
guage, it can work with variables and make decisions. 

<?php 

echo "hi , mom. " ; 

$var = dateC'H") ; 
if ($var <= 11) 

echo "good morni ng" ; 
else if ($var > 11 and $var < 18) 

echo "good afternoon"; 
else 

echo "good evening"; 

?> 

In this page, after printing out the greeting, there is some real programming. I've 
used PHP's built-in date function to grab the hour of the day in 24-hour format. 
That value is immediately assigned to a variable named $var. Then a decision is 
made, and the appropriate text is printed, depending on the time of day. Notice the 
syntax here. Each PHP command ends with a semicolon. In the if statement, curly 
braces hold the commands to be executed depending on the condition. And the 
condition itself is held within parentheses. 



xxxiv Introduction 



The date( ) function and echo, which are used in the previous example, arejust 
two of the hundreds of functions built into PHP, many of which you will learn to 
use in the course of this book. If you are going to access the database, you're going 
to need a few more. 

CONNECTING TO THE DATABASE 

Whileyou're installing PHP, you should let it know that you plan on using MySQL with 
it. If you don't do this, what we will discuss now won't work. Even if PHP is aware that 
you're using MySQL, in your specific scripts you must identify the exact database you 
need access to. In this case, that will be the guestbook database you just created. 

mysql_connect( "1 ocal host" , "nobody" , "password" ) or 

die ("Could not connect to database"); 
mysql_sel ect_db( "guestbook") or 

die ("Could not select database"); 

The first line tells MySQL that the Web server (the entity running the script) is on 
the local machine, has a username of nobody, and has a password of password. 
Then, if the connection is successful, the specific database is selected with the 
mysqi_seiect_db( ) command. With these lines safely tucked away in your scripts, 
you should be able to manipulate the database with your commands. 

Because you're going to need these lines in every page in this application, it 
makes sense to save some typing and put them in a file of their own and include 
them in every page. If you've done any programming at all, you know that this 
involves dumping the entire contents of that file into the file being accessed. These 
lines will be kept in a file called dbconnect.php. At the top of every other file in this 
application will be the following line: 

include('dbconnect.php'); 

INSERTING INFORMATION INTO THE DATABASE 

Because you have yet to put any users in the database, we'll start by reviewing the 
script that will allow that. But first, we need to tell you a little bit more about PHP 
variables. A bit earlier, we showed that you can create variables within a PHP script, 
but as this is a client/server environment, you're going to need to get variable data 
from the client (the Web browser) to PHP. You'll usually do this with HTML forms. 

There's a basic rundown of HTML forms in Appendix A. Check that if you need 
to. For now we will just point out that every form element has a name, and when a 
form is submitted the names of those form elements become available as variables 
in the PHP script the form was submitted to. With the following form, as soon as 
the form is submitted, the variables $surname and $submit will become available 
in the PHP script myscript.php. The value of $surname will be whatever the user 
enters into the text field. The value of $submit will be the text string "submit." 

<form acti on="myscri pt . php"> 

<input type="text" name="surnmae"> 



Introduction xxxv 



<input type="submi t" name="submi t" val ue="submi t"> 
</f orm> 

Before we show the script itself, now is a good time to note that Web program- 
ming is slightly different from other types of programming in one important 
respect: It is stateless. To display a page, a Web server must first receive a request 
from a browser. The language they speak is called HTTP, the Hypertext Transfer 
Protocol. The request will include several things— the page the browser wishes to 
see, the form data, the type of browser being used, and the IP address the browser 
is using. Based on this information, the Web server will decide what to serve. 

Once it has served this page, the server maintains no connection to the browser. 
It has absolutely no memory of what it served to whom. Each HTTP request is dealt 
with individually with no regard to what came before it. For this reason, in Web 
programming you need to come up with some way of maintaining state. That is, if 
you are progressing through an application, you will need some way of letting the 
server know what happened. Essentially, you will need ways of passing variables 
from page to page. This will come up in our applications. The applications will 
solve this problem in one of three ways: by passing hidden form elements, by using 
cookies, or by using sessions. 

Now back to our script. 

<form acti on="myscri pt . p h p " > 

<input type="text" name="surnmae"> 

<input type="submi t" name="submi t" val ue="submi t"> 

</f orm> 

You can decide what you will display on a page based on the variable informa- 
tion that comes from HTML forms. For instance, you could check if the preceding 
form had been submitted by checking if the variable name $submit had a value of 
"submit." This very technique will come into play when it comes to creating the 
page for inserting information into the database. 

There is one page in our application, called sign.php, that has an HTML form. 
The action of the form in this page is create_entry.php. Here's the page in all its 
glory: 

< h 2 > S i g n my Guest Book!!!</h2> 

<form method=post acti on="create_entry .php"> 

<b>Name:</b> 

<input type=text size=40 name=name> 

<br> 

<b>Locati on : </b> 

<input type=text size=40 name=l ocati on> 

<br> 



xxxvi Introduction 



<b>Email :</b> 

<input type=text size=40 name=email> 

<br> 

<b>Home Page URL:</b> 

<input type=text size=40 name=url> 

<br> 

<b>Comments : </b> 

<textarea name=comments cols=40 rows=4 wrap=vi rtual ></textarea> 

<br> 

<input type=submit name=submit val ue="Si gn ! "> 
<input type=reset name=reset value="Start Over"> 

</form> 

When the user fills out this form and submits it, the information will be sent to 
create_entry.php. The first thing to do on this page is to check that the form has 
been submitted. If it has, take the values entered into the form and use them to cre- 
ate a query that you will send to MySQL. Don't worry about the specifics of the 
query just yet. J ust know that it will insert a row into the database table you cre- 
ated earlier. 

<?php 
include("dbconnect.php"); 

i f ( $submi t == "Si gn ! " ) 

{ 

$query = "insert into guestbook 

(name.location.email , url , comments ) val ues 

('$name', '$location', 'lemail', '$url', ' Scomments ' ) " 

mysql_query ( $ query ) or 

die (mysql_error( ) ) ; 
?> 

<h2>Thanks! !</h2> 

<h2Xa href="view.php">View My Guest Book! ! ! </a></h2> 
<?php 
) 

el se 
{ 

i n c 1 u d e ( " s i g n . p h p " ) ; 
} 
?> 

If the form, which is in sign.php, hasn't been submitted, it is included and there- 
fore will show the same form. You may notice that this page is submitted to itself. 



Introduction xxxvii 



The first time the create_entry.php page is called, the form in sign.php will be dis- 
played. The next time, though, the data will be inserted into the database. 
Figures 1-2 and 1-3 show the pages that this script will create. 



'a http://madfishxom:8080/book/guestbook/sign.php - Microsoft Internet Explorer 



File Edit View Favorites Tools Help 



Address | http://192.1G8.1.i;book/guestbook/sign.php 



4" , ■+ , @ © 4 

Back Stop Refresh Home 



Sign my Guest Book!!! 



Name: |Jay Greenspan 



Location: |San Francisco 



Email: |jgreen_1 ©yahoo. cam 



Home Page UR1: |http://www.phpmysqlbook.com 



Comments: 

| Sign! | Start Over | 



~3 



~3 ^G 



© a &- m m 

S eaich Favoriles H istory Mail Print E dit 



J 



il«Start| ^ <D & m ±3 '£ 1,1 [g] W\M 3Vff)°'X>i (jy^t'gl 3:41 AM 



Figure 1-2: create entry.php the first time through 



a http://madfish.com:8080/book/guestbook/create entry.php - Microsoft Internet Explorer 



131 



File Edit View Favorites Tools Help 



Add re s s M£| http:i7modfish.com: efjE:0'buok/guestpook/create_entry.php 



^3 r>G 



4" , ■+ . @ © 4 

Back Forward Stop Refresh Home 



Seaich Favoriles H 



S' # 

Mail Print 



Thanks!! 



View My Guest Book!!! 



ii«Start| g <3j <P [§ IB ^ ^ Ij gj|[& J^H ||J%Wfft°%i^<feiJ*q> i fo% 3:42 



AM 



Figure 1-3: create entry.php after submission 



xxxviii Introduction 



VIEWING INFORMATION IN THE DATABASE 

This shouldn't be too tough. You already know that the file will need to include 
dbconnect.php. Other than that, we've already mentioned that databases store 
information in tables. Each row of the table will contain information on a specific 
person who signed the guestbook, so to view all of the information, the page will 
need to retrieve and printout every row of data. Here's the script that will do it (you 
should notice that it's pretty sparse): 

<?php include(" dbconnect.php"); ?> 

<h2>View My Guest Book!!</h2> 

<?php 

Iresult = mysql_query ( "sel ect * fnom guestbook") or 

die ( my s q 1 _e r r o r ( ) ) ; 
while ($row = mysql_fetch_array ( Iresul t ) ) 
{ 

echo "<b>Name:</b>" ; 

echo $row[ "name"] ; 

echo "<br>\n"; 

echo "<b>Locati on : </b>" ; 

echo $row[ "1 ocati on"] ; 

echo "<br>\n"; 

echo "<b>Emai 1 : </b>" ; 

echo $row[ "emai 1 " ] ; 

echo "<br>\n"; 

echo "<b>URL:</b>"; 

echo $row[ "url "] ; 

echo "<br>\n"; 

echo "<b>Comments : </b>" ; 

echo $row[ "comments"] ; 

echo "<br>\n"; 

echo "<br>\n"; 

echo "<br>\n"; 
} 

mysql_free_result($result) ; 
?> 

<h2Xa href="sign.php">Sign My Guest Book! ! </a></h2> 



Introduction 



xxxix 



The query asks MySQL for every row in the database. Then the script enters a 
loop. Each row in the database is loaded into the variable $row, one row at a time. 
Rows will continue to be accessed until none is left. At that time, the script will 
drop out of the while loop. 

As it works through the loop, each column in that row is displayed. For example 

print $row[ "emai 1 " ] 

will print out the e-mail column for the row being accessed. 

When run, this simple script will print out every row in the database. Figure 1-4 
shows what the page will look like. 



I http://192.168. 1.1/book/guestbook/view.php - Microsoft Internet Explorer 



File Edit View Favorites lools Help 



Address |@ http://192.1G8. 1.1/book/guestbook/view.php 



"31 (f^Go 



> . -► . & m a 

Back Stop Refresh Home 



m. a 

Search Favorites History 



View My Guest Book!! 

Name: Jay Greenspan 
Location: San Francisco 
Email:jgreen_l@yahoo.com 
tnRL:http://www. trans-city, com 
Comments:! love it. 



Name: John Doe 

L o c ation: Norway 

Email: j do e @no domain, c om 

UE1: 

Comments: Hello there. 



jj 



jjastartl m^V&m^^ _gj|[£) ;jd 3Vff>»'X^ «ffl^t' g B*J 9:58 AM 

Figure 1-4: view.php 



And that about does it for our first application. 

WHY YOU SHOULD NOT USE THIS APPLICATION 

If you want to load this up on your own server to see if it works, fine; be our guest. 
But we wouldn't put it anywhere where the general public could get to it. No, if you 
were to do that there would be problems. For instance, you could end up with 
Figure 1-5 on your view.php page. Not good at all! 



xl 



Introduction 



-' http://madfish.com:8080/book/guestbook/view.php - Microsoft Internet Explorer 



File Edit View Favorites lools Help 



Address | *S] http: //madfish. com: 8080/book/guestbook/view. php 



~ZJ <^ G ° 



Back Forward 



Stop 



Refresh 



® a 

Search Favorites History 



Mail 



Print 



Edit 



NameiBrittney fan 

Location: 

Email: 

URL: 




lastartl ^ ^ <P a ■ J3 23 4j|g] jj 3Vff)°'X>i d§^Ot' j B*J 10 



:00 AM 



Figure 1-5: Problematic guestbook entry 



If you want a guestbook, you should use the super- hyper- coded application 
made exclusively for the readers of this book, which you will find in Chapter 8. 
We call this application Guestbook2k. But before we get there, it's time for some 
education. 

Now get reading. 



Chapter 1 

Database Design with 
MySQL 

IN THIS CHAPTER 

♦ Identifying the problems that led to the creation of the relational database 

♦ Learning the normalization process 

♦ Taking a look at database features that MySQL does not currently support 



The bulk of this chapter is for those of you who have made it to the early 21 st 
century without working with relational databases. If you're a seasoned database 
pro, having worked with Oracle, Sybase, or even something like Microsoft Accessor 
Paradox, you may want to skip this little lesson on database theory. However, I do 
suggest that you look at the final section of this chapter, where I discuss some of 
MySQL's weirder points. MySQL's implementation of SQL is incomplete, so it may 
not support some of what you might be looking for. 



Why Use a Relational Database? 

If you're still here and are ready to read with rapt attention about database theory 
and the wonders of normalization, you probably don't know much about the history 
of the relational database. You may not even care. For that reason, I'll keep this very 
brief. Dr. E. F. Codd was a research scientist at IBM in the 1960s. A mathematician 
by training, he was unhappy with the available models of data storage. He found 
that all the available methods were prone to error and redundancy. He worked on 
these problems and then, in 1970, published a paper with the rousing title "A 
Relational Model of Data for Large Shared Databanks." In all honesty, nothing has 
been the same since. 

A programmer named Larry Ellison read the paper and started work on software 
that could put Dr. Codd's theories into practice. If you've been a resident of this 
planet over the past 20 years, you may know that Ellison's product and company 
took the name Oracle and that he is now one of the richest men in the world. His 
earliest product was designed for huge mainframe systems. Responding to market 
demands over the years, Oracle, and many other companies that have sprung up 
since, have designed systems with a variety of features geared toward a variety of 



Part I: Working with MySQL 

operating systems. Now, relational databases are so common that you can get one 
that runs on a Palm Pilot. 

To understand why Dr. Codd's theories have revolutionized the data storage 
world, it's best to have an idea as to what the troubles are with other means of data 
storage. Take the example of a simple address book— nothing too complex, just 
something that stores names, addresses, phone numbers, e-mails, and the like. If 
there's no persistent, running program that we can put this information into, the 
file system of whatever OS is running becomes the natural choice for storage. 

For a simple address book, a delimited text file can be created to store the infor- 
mation. If the first row serves as a header and commas are used as the delimiter, it 
might look something like this: 

Name, Addrl, Addr2, City, State, Zip, Phone, E-mail 

Jay Greenspan, 211 Some St, Apt 2, San Francisco, CA, 94107, 

4155551212, jgreen_l@yahoo.com 

Brad Bulger, 411 Some St, Apt 6, San Francisco, CA, 94109, 

4155552222, bbulger@yahoo.com 

John Doe, 444 Madison Ave, , New York, NY, 11234, 2125556666, 

nobody@hotmai 1 .com 

This isn't much to look at, but it is at least machine- readable. Using whatever lan- 
guage you wish, you can write a script that opens this file and then parses the infor- 
mation. You will probably want it in some sort of two-dimensional or associative 
array so that you'll have some flexibility in addressing each portion of each line of 
this file. Any way you look at it, there's going to be a fair amount of code to write. If 
you want this information to be sortable and queryable by a variety of criteria, you're 
going to have to write scripts that will, for instance, sort the list alphabetically by 
name or find all people within a certain area code. What a pain. 

You might face another major problem if your data needs to be used across a net- 
work by a variety of people. Presumably more than one person is going to need to 
write information to this file. What happens if two people try to make changes at 
once? For starters, it's quite possible that one person will overwrite another's changes. 
To prevent this from happening, the programmer has to specify file locking if the 
file is in use. While this might work, it's kind of a pain in the neck for the person 
who gets locked out. Obviously, the larger the system gets the more unmanageable 
this all becomes. 

What you need is something more robust than the file system— a program or 
daemon that stays in memory seems to be a good choice. Further, you'll need a data 
storage system that reduces the amount of parsing and scripting that the program- 
mer needs to be concerned with. No need for anything too arcane here. A plain, 
simple table like Table 1-1 should work just fine. 

Now this is pretty convenient. It's easy to look at and, if there is a running pro- 
gram that accesses this table, it should be pretty quick. What else might this 
program do? First, it should be able to address one row at a time without affecting 



Chapter 1: Database Design with MySQL 

the others. That way, if two or more people want to insert information into this 
table, they won't be tripping over each other. It would be even spiffier if the 
program provided a simple and elegant way to extract information from a table 
such as this. There should be a quick way of finding all of the people from 
California that doesn't involve parsing and sorting the file. Furthermore, this 
wondrous program should be able to accept statements that describe what you 
want in a language very similar to English. That way you can just say: "Give me all 
rows where the contents of the State column equal 'CA'." 

Yes, this would be great, but it isn't enough. There are still major problems that 
will need to be dealt with. These problems, which I'll discuss in the following pages, 
are the same ones that made Dr. Codd write his famous paper, and that made Larry 
Ellison a billionaire. 



Blasted Anomalies 

Dr. Codd's goal was to have a model of information that was dependable. All of the 
data-storage methods available to him had inherent problems. He referred to these 
problems as anomalies. There are three types of anomalies: Update, Delete, and Insert. 

Update anomaly 

Now that we can assume that a table structure can quickly and easily handle mul- 
tiple requests, we need to see what happens when the information gets more com- 
plex. Adding some more information to the previous table introduces some serious 
problems (Table 1-2). 

Table 1-2 is meant to store information for an entire office, not just a single per- 
son. Since this company deals with other large companies, there will betimes when 
more than one contact will beat a single office location. For example, in Table 1-2, 
there are two contacts at 1121 43 rd St. At first this may appear to be OK: we can still 
get at all the information available relatively easily. The problem comes when the 
BigCo Company decides to up and move to another address. In that case, we'd have 
to update the address for BigCo in two different rows. This may not sound like 
such an onerous task, but consider the trouble if this table has 3,000 rows instead 
of 3— or 300,000 for that matter. Someone, or some program, has to make sure the 
data is changed in every appropriate place. 

Another concern is the potential for error. It's very possible that one of these 
rows could be altered while the other one remained the same. Or, if changes are 
keyed in one row at a time, it's likely that somebody will introduce a typo. Then 
you're left wondering if the correct address is 1121 or 1211. 

The better way to handle this data is to take the company name and address and 
put that information in its own table. The two resulting tables will resemble Table 
1-3 and Table 1-4. 



co 



Table X- 1 SIMPLE TABLE FOR DATA STORAGE 


2? 


name 

J ay Greenspan 
Brad Bulger 
John Doe 


addrl 

211 Some St 
411SorreSt 
444 Madison Ave 


addr2 

Apt2 
Apt 6 


city 

San Francisco 
San Francisco 
New York 


state 

CA 
CA 
NY 


zip 

94107 
94109 
11234 


phone 

415555171? 
4155552222 


e-mail 

jgreen_l@yahoo .com 
bbul ger@yahoo . com 
nobody@hotmai 1 .com 


A- 

o 
-« 

3" 


2125556666 


S 
3- 

3" 
>< 

to 



r- 





Table 1-2 PROBLEMATIC TABLE STORAGE 



id company^ name 

1 BigGo Company 

2 BigCo Gorrpany 

3 LittleGo Gorrpany 



CQ rrp ariyr_adUnesB 

1121 43 rd St 
1121 43 rd St 
/]/]/]/] 44 th st 



contact name 

J ay Greenspan 
Brad Bulger 
John Doe 



contact_tit:le 

Vice President 

President 

Lackey 



phone 

4155551212 
4155552222 
2125556666 



jgreen_l@yahoo. com 
bbul ger@yahoo. com 
nobody@hotmai 1 .com 



Table 1-3 COMFKNIES 



Cjon-pany_id 


Gorrpany_name 


cc«iiaany_acluress 


1 


BigGo Company 


1121 43 rd St 


2 


LittleGo Corrpany 


/]/]/]/] 44 th st 



Table 1-4 GON17VCT5 



CO 


ntactid 


cxjrrpanyi 


id 


cjonbactnarre 


contact title 


phone 


email 


1 




1 




J ay Greenspan 


Vice President 


4155551212 


jgreen_l@yahoo . com 


2 




1 




Brad Bulger 


President 


4155552222 


bbul ger@yahoo. com 


3 




2 




John Doe 


Lackey 


2125556666 


nobody@hotmai 1 .com 



o 

3- 

su 

ff 






a 



>< 
8 



Part I: Working with MySQL 

Now the information pertinent to BigCo Co. is in its own table, named Companies. 
If you look at the next table (Table 1-4), named Contacts, you'll see that we've 
inserted another column, called companyjd. This column references the companyjd 
column of the Company table. In Brad's row, we see that the companyjd (the second 
column) equals 1. We can then go to the Companies table, look at the information for 
companyjd 1 and see all the relevant address information. What's happened here is 
that we've created a relationship between these two tables— hence the name rela- 
tional database. 

We still have all the information we had in the previous setup, we've just seg- 
mented it. In this setup we can change the address for both J ay and Brad by altering 
only a single row. That's the kind of convenience we're after. 

Perhaps this leaves you wondering how we get this information un-segmented. 
Relational databases give us the ability to merge, or join, tables. Consider the fol- 
lowing statement, which is intended to give us all the available information for 
Brad: "Give me all the columns from the contacts table where contactjd is equal to 
1, and while you're at it throw in all the columns from the Companies table where 
the companyjd field equals the value shown in Brad's companyjd column." In 
other words, in this statement, you are asking to join these two tables where the 
companyjd fields are the same. The result of this request, or query, would look 
something like Table 1-5. 

In the course of a couple of pages, you've learned how to solve a data-integrity 
problem by segmenting information and creating additional tables. But I have yet 
to give this problem a name. When I learned the vocabulary associated with rela- 
tional databases from a very thick and expensive book, this sort of problem was 
called an update anomaly. There may or may not be people using this term in the 
real world; if there are, I haven't met them. However, I think this term is pretty apt. 
In the table presented earlier in this section, if we were to update one row in the 
table, other rows containing the same information would not be affected. 



Delete anomaly 



Now take a look at Table 1-6, focusing on row 3. Consider what happens if Mr. Doe 
is deleted from the database. This may seem like a simple change but suppose 
someone accessing the database wants a list of all the companies contacted over 
the previous year. In the current setup, when we remove row 3 we take out not only 
the information about John Doe, we remove information about the company as 
well. This problem is called a deletion anomaly. 

If the company information is moved to its own table, as we saw in the previous 
section, this won't be a problem. We can remove Mr. Doe and then decide indepen- 
dently if we want to remove the company he's associated with. 



Table 1-5 QUERY RESULTS 



cxarrpany_ 
company_id name 



company_ 
address 



BigCb Gotrpany 1121 43 ra St 



Name 



Contact 
Title 



id Name Title Phone E-mail 

Brad Bulger President 4155552222 bbul ger@yahoo . com 



Table 1-6 TABLE WITH DELETION ANOMALY 



company_ 



company_ 

address 



company_id 

1 BigGo Gorrpany 

2 BigGo Gorrpany 1121 43 m St 

3 UttleGo Gorrpany 4444 44 th St 



1121 43 rd St 
and « 



co n tac t , 

name LiUe 

J ay Greenspan Vice Preadent 

Brad Bulger Preadent 

J ohn Doe Lackey 



phone 

4155551212 
4155552222 
2125556666 



jgreen_l@yahoo. com 
bbul ger@yahoo. com 
nobody@hotmai 1 .com 



o 

3" 

ff 



cu 



a 



>< 



LO 



10 Part I: Working with MySQL 

Insert anomaly 

Our final area of concern is problems that will be introduced during an insert. 
Looking again at the Table 1-6, we see that the purpose of this table is to store 
information on contacts, not companies. This becomes a drag if you want to add a 
company but not an individual. For the most part, you'll have to wait to have a 
specific contact to add to the data before you can add company information to the 
database. This is a ridiculous restriction. 



Normalization 



Now that we've shown you some of the problems you might encounter, you need to 
learn the ways to find and eliminate these anomalies. This process is known as nor- 
malization. Understanding normalization is vital to working with relational data- 
bases. But, to anyone who has database experience, normalization is not the be-all 
and end-all of data design. Experience and instinct also play a part in creating a 
good database. In the examples presented later in this book, the data will be nor- 
malized, for the most part— but there will also be occasions when an unnormalized 
structure is preferable. 

One other quick caveat. The normalization process consists of several "normal 
forms." In this chapter we will cover 1 st , 2 nd , and 3 rd normal forms. In addition to 
these, the normalization process can continue through four other normal forms. (For 
the curious, these are called Boyce-Codd normal form, 4 th normal form, 5 th normal 
form, and Domain/Key normal form). I know about these because I read about them 
in a book. In the real world, where real people actually develop database applications, 
these normal forms just don't get talked about. If you get your data into 3 rd normal 
form that's about good enough. Yes, there is a possibility that anomalies will exist in 
3 rd normal form, but if you get this far you should be OK. 

1 st normal form 

Getting data into 1 st normal form is fairly easy. Data need to be in a table structure 
and meet the following criteria: 

♦ Each column must contain an "atomic" value. That means that there will 
be only one value per cell. No arrays or any other manner of representing 
more than one value will exist in any cell. 

♦ Each column must have a unique name. 

♦ The table must have a set of values that uniquely identifies the row (This 
is known as the primary key of the table). 

♦ No two rows can be identical. 

♦ No repeating groups of data are allowed. 



Table 1-7 TABLE WITH REPEATING GROUPS OF DATA 



caivany 



carrppny_icl 

1 
2 
3 



carrpariy_ 

acUcss 

and , 



BigGo Gorrpany 

BigGo Gotrpany 1121 43 m St 

UttleGo Gorrpany 4444 44 th St 



1121 43 m St 

.rd < 



contact_ 

name titJe 

J ay Greenspan Vice President 

Brad Bulger President 

J ohn Doe Lackey 



phone 

4155551212 
4155552222 
2125556666 



jgreen_l@yahoo. com 
bbul ger@yahoo. com 
nobody@hotmai 1 . com 



CD 








CD 




l/l 




r- r 




3" 




CD 




O 




Z3 




"< 




o 




Z3 




CD 




r- r 




3" 


r» 


QJ 


3" 


3 




ai 


i-r 


-< 


fD 




■n 






CD 

iS 


I- 1 


c 






a 




St 


3 




CD 


U) 


CD 


n> 


X 

T3 


a 


QJ 


9! 



tQ 



QJ 

in 

QJ 

o~ 
o 

7\" 



>< 

to 
O 



12 Part I: Working with MySQL 

As we've already seen, row 1 and row 2 contain two columns that contain iden- 
tical information. This is a repeating group of data. Only when we remove these 
columns and place them in their own table will this data be first normal form. The 
separation of tables that we did in Tables 1-3 and 1-4 will move this data into 1 st 
normal form. 

Before we move on to chat about 2 nd and 3 rd normal form, you're going to need 
a couple of quick definitions. The first is primary key. The primary key is a column 
or set of columns by which each row can be uniquely identified. In the tables pre- 
sented so far, I've included a column with sequential values, and as rows are added 
to these tables the database engine will automatically insert an integer one greater 
than the maximum value for the column. It's an easy way to make sure you have a 
unique field for every row. Every database in the world has some method of defin- 
ing a column like this. In MySQL it's called an autojncrement field. Depending on 
your data, there are all kinds of values that will work for a primary key. Social 
Security numbers work great, as do e-mail addresses and URLs. The data just need 
to be unique. In some cases, two or more columns may comprise your primary key. 
For instance, continuing with our address book example, if contact information 
needs to be stored for a company with many locations, it will probably be best to 
store the switchboard number and mailing address information in a table that has 
the companyjd and the company city as its primary key. 

Next, we need to define the word dependency, which means pretty much what 
you think it means. A dependent column is one that is inexorably tied to the pri- 
mary key. It can't exist in the table if the primary key is removed. 

With that under our belts, we are ready to tackle 2 nd normal form. 



2 nd normal form 



This part of the process only comes into play when you end up with one of those 
multi-column primary keys that I just discussed. Assume that in the course of 
dividing up our address tables we end up with Table 1-8. Here, the company_name 
and comapanyjocation comprise the multi-column primary key. 



Table 1-8 TABLE NOT IN 2 nd NORMAL FROM 

companyname company_ location companyceo company_ address 

BigCo Company San Francisco Bill Hurt 1121 43 rd St 

LittleCo Company LA LittleCo Company 4444 44 th st 



You should be able to see pretty quickly that an insertion anomaly would work 
its way in here if we were to add another location for BigCo Co. We'd have the CEO 
name, Bill Hurt, repeated in an additional row, and that's no good. 



Chapter 1: Database Design with MySQL 13 

We can get this table into 2 nd normal form by removing rows that are only partially 
dependent on the primary key. Here, CEO is only dependent on the company_name 
column. It is not dependent on the companyjocation column. To get into 2 nd normal 
form, we move rows that are only partially dependent on a multi-field primary key 
into their own table. 2 nd normal form does not apply to tables that have a single- 
column primary key. 

3 rd normal form 

Finishing up the normalization process, 3 rd normal form is concerned with transitive 
dependencies. A transitive dependency is a situation where a column exists that is 
not directly reliant on the primary key. Instead, the field is reliant on some other 
field, which in turn is dependent on the primary key. A quick way to get into 3 rd 
normal form is to look at the all fields in a table and ask if those fields describe the 
primary key. If not, you're not there. 

If your address book needs to store more information on your contacts, you may 
find yourself with a table like this. 



Table 1-9 TABLE NOT IN 3 rd 


NORMAL FORM 






contact 


contact 


assistant 


assistant 


contactid name 


phone 


name 


phone 


1 Bill J ones 


4155555555 


John Bills 


2025554444 


2 Carol Shaw 


2015556666 


Shawn Carlo 


6505556666 



You may think we're doing OK here. But look at the assistant_phone column and 
ask if that really describes the primary key (and the focus of this table), which is 
your contact. It's possible, even likely, that one assistant will serve many people, in 
which case it's possible that an assistant name and phone will end up listed in the 
table more than once. That would be a repeating group of data, which we already 
know we don't want. 



Types of Relationships 

Essentially, the deal with this book is that we're going to create a bunch of tables that 
don't have anomalies. We'll include columns that maintain relationships between 
these tables. There are three specific types of relationships that we'll encounter in 
database land. 



14 



Part I: Working with MySQL 



One- to- many relationship 



This is by far the most common type of relationship that occurs between tables. When 
one value in a column references several fields in another table, a one-to-many 
relationship is in effect (Figure 1-1). 



Industries 



Companies 



IndustryJD 



Companyjd 


Company_Name 


Industryjd 


1 


Big Co Corporation 


1 


2 


Little Co Corporation 


1 


3 


Joe's Utility 


1 


4 


Leadfoot Builders 


2 


5 


Angel's Cement Boots 


2 


6 


Al's Bank 


3 



Figure 1-1: Tables with a one- to- many relationship 



lndustry_name 



Utilities 



Construction 



Banking 



This is a classic one-to many relationship. Here, each company is associated with 
a certain industry. As you can see, one industry listed in the industry table can be 
associated with one or more rows in the company table. This in no way restricts 
what we can do with the companies. We are absolutely free to use this table as the 
basis for other one-to-many relationships. Figure 1-2 shows that the Companies 
table can be on the "one" side of a one- to-many relationship with a table that lists 
city locations for all the different companies. 



One-to-one relationship 



A one-to-one relationship is essentially a one- to-many relationship where only one 
row in a table is related to only one row in another table. During the normalization 
process, I mentioned a situation where one table holds information on corporate 
executives and another holds information on their assistants. This could very well 
be a one-to-one relationship if each executive has one assistant and each assistant 
works for only one executive. Figure 1-3 gives a visual representation 



Chapter 1: Database Design with MySQL 



15 



Companies 



Industries 



IndustryJD 



lndustry_name 



Utilities 



Construction 



Banking 



Companyjd 


Company_Name 


Industryjd 


1 


Big Co Corporation 


1 


2 


Little Co Corporation 


1 


3 


Joe's Utility 


1 


4 


Leadfoot Builders 


2 


5 


Angel's Cement Boots 


2 


6 


Al's Bank 


3 



Co Location id 



Companyjd 



2 



city 



San Francisco 



New York 



Chicago 



Dallas 



Figure 1- 2: Tables with two one- to- many relationships 



Executives 





ExecID 


Exec first name 


Exec last name 






r 1 


Jon 


Dust 






- 2 


Melinda 


Burns 






-3 


Larry 


Gains 






Assistants 






Asstjd 


Execjd 


Asst_first_name 


Asst_last_name 




■1 


Walter 


James 






•} 


2 


James 


Walter 






7 


.3 




Els 



















Figure 1- 3: Tables with a one-to-one relationship 



16 Part I: Working with MySQL 

Many- to- many relationship 

Many-to-many relationships work a bit differently from the other two. For instance, 
suppose that the company keeping the data has a variety of newsletters that it sends 
to its contacts, and it needs to add this information to the database. There's a weekly, 
a monthly, a bi-monthly, and an annual newsletter, and to keep from annoying 
clients, the newsletters must only be sent to those who request them. 
To start, you could add a table that stores the newsletter types (Table 1-10) 



Table 1-10 NEWSLETTERS TABLE 



newsletter_ id 


newslettername 


1 


Weekly 


2 


Monthly 


3 


Bi-monthly 


4 


Annual 



Table 1-10 can't be directly related to another table that stores contact information. 
The only way to make this work is to add a column that stores the newsletters that 
each contact receives. Right away, you should notice a problem with the Table 1-11. 



Table 1-11 CONTACTS TABLE 



contact_ id 


contactfirstname 


contactlastname 


newsletters 


1 


Jon 


Doe 


1,3,4 


2 


Al 


Banks 


2,3,4 



In this table the Newsletters column contains more than one value. The value 
looks a lot like an array. As mentioned earlier, this should never occur within your 
database— we want only atomic values in each column. 

In situations like this you'll need to create another table. Figure 1-4 shows how 
the relationship between these values can be made to work. 



Chapter 1: Database Design with MySQL 



17 



Contact id 


Contact first name 


Contact last name 


1 


Jon 


Doe 


2 


Al 


Banks 








Newsletter id 


Newsletter name 








1 


Weekly 






r 


2 


Bi-Weekly 




3 


Annual 




4 


Semi-annual 




1 




































Client id 


Newsletter id 






1 


1 




1 


2 






2 


2 




2 


3 




2 





















Figure 1-4: Tables with a many- to- many relationship 

With this structure, any number of contacts can have any number of newsletters 
and any number of newsletters can be sent to any number of contacts. 



Features MySQL Does Not Support 

MySQL is a polarizing piece of software in the applications development commu- 
nity. It has aspects that many developers like: it's free, it doesn't take up a whole lot 
in the way of resources, it's very quick, and it's easy to learn compared to packages 
like Oracle and Sybase. However, MySQL achieves its speediness by doing without 
features common in other databases, and these shortcomings will keep many from 
adopting MySQL for their applications. But, for many, the lack of certain features 
shouldn't be much of a problem. Read and decide for yourself. 

Referential integrity 

Every example used in the previous pages made use of foreign keys. A foreign key is 
a column that references the primary key of another table in order to maintain a 
relationship. In Table 1-4, the Contacts table contains a companyjd column, which 
references the primary key of the Companies table (Table 1-3). This column is a 
foreign key to the Companies table. 



18 Part I: Working with MySQL 

In Chapter 2 we demonstrate how to create tables in MySQL. It's easy enough to 
create tables with all the columns necessary for primary keys and foreign keys. 
However, in MySQL foreign keys do not have the significance they have in most 
database systems. 

In packages like Oracle, Sybase, or PostGres, tables can be created that explicitly 
define foreign keys. For instance, using the tables 1-3 and 1-4 with Oracle, the 
database system could be made aware that the companyjd column in the Contacts 
table had a relationship to the Companies table. This is potentially a very good 
thing. If the database system is aware of a relationship, it can check to make sure 
the value being inserted into the foreign key field exists in the referenced table. If it 
does not, the database system will reject the insert. This is known as referential 
integrity. 

To achieve the same effect in MySQL, the application developer must add some 
extra steps before inserting or updating records. For example, to be ultra-safe, 
the programmer needs to go through the following steps in order to insert a row in 
the Contacts table (1-4): 

1. Get all of the values for companyjd in the Companies table. 

2. Check to make sure the value for companyjd you are going to insert into 
the Contacts table exists in the data you retrieved in step 1. 

3. If it does, insert values. 

The developers of MySQL argue that referential integrity is not necessary and 
that including it would slow down MySQL. Further, they argue that it is the respon- 
sibility of the application interacting with the database to ensure that the inserted 
data is correct. There is logic to this way of thinking. In Parts III and IV of this book 
we present several applications that all work just fine without enforcing referential 
integrity or the method of checking shown above. In general, in these applications, 
all the possible values are pulled from a database anyway and there's very little 
opportunity for errors to creep into the system. 

For example, using PHP and HTM L, the programmer might turn the Companies 
table into a drop-down box. That way the user can only choose a valid value. 

Transactions 

In relational databases, things change in groups. As shown in a variety of appli- 
cations in this book, many changes require that rows be updated in several tables 
concurrently. In some cases, tables may be dropped as part of a series of statements 
that get the data where it needs to be. An e-commerce site may contain code like 
the following: 

1. Insert customer into the Customers table. 

2. Add invoice information into the Invoice table. 

3. Remove a quantity of 1 of ordered item from the Items table. 



Chapter 1: Database Design with MySQL 



19 




By the time you read this book, there is a very good chance that MySQL will 
support transactions. The developers have been working with transaction- 
safe tables for some time. Check mysql.com to see if the current release can 
use transactions. We did not use transactions in any of the applications in 
this book. 



When you're working with a series of steps like this, there is potential for serious 
problems. If the operating system crashes or power goes out between steps two and 
three, the database will contain bad data. 

To prevent such a state, most sophisticated database systems make use of transac- 
tions. With transactions, the developer can identify a group of commands. If any one 
of these commands fails to go through, the whole group of commands is nixed and the 
database returns to the state it was in before the first command was attempted. This is 
known a COM M IT/ROLLBACK approach. Either all of the requests are committed to the 
database, or the database is rolled back to the state it was in prior to the transactions. 

In Section 5.4.3 of the MySQL Reference Manual, there is a lengthy defense of 
MySQL's choice not to include transactions. It also includes techniques for achieving 
the same effect with logging and table locks. You can decide for yourself whether 
this argument makes sense. Many people feel that the lack of transactions makes 
MySQL a poor choice in certain environments. If you're the IT manager at a bank 
looking for a relational database management system (RDBMS), MySQL probably 
isn't the way to go. 



Stored procedures 



The big fancy database systems allow for procedural code (something very much like 
PHP or Perl) to be placed within the database. There are a couple of key advantages 
to using stored procedures. First, it can reduce that amount of code needed in mid- 
dleware applications. If MySQL accepted stored procedures, a single PHP command 
can be sent to the database to query data, do some string manipulation, and then 
return a value ready to be displayed in your page. 

The other major advantage comes from working in an environment where more 
than one front-end is accessing the same database. Consider a situation where there 
happens to be a front-end written for the Web and another in Visual C++ accessible 
on Windows machines. It would be a pain to write all the queries and transactions in 
two different places. You'd be much better off writing stored procedures and access- 
ing those from your various applications. 




MySQL also does not support sub-selects. We will discuss how to work around 
this limitation in Chapter 3. 



20 Part I: Working with MySQL 



Summary 



At this point you should have a pretty good idea of how relational databases work. The 
theory covered here is really important, as quality data design is one of the corner- 
stones of quality applications. If you fail in the normalization process, you could leave 
yourself with difficulties that will haunt you for months or years. 

In the applications in Parts 3 and 4 of this book, you will see how we approached 
and normalized several sets of data. 

Now that you know how tables in a relational database work, move on to 
Chapter 2, where you will see how to make these tables in MySQL. 



Chapter 2 

The Structured Query 
Language for Creating 
and Altering Tables 

IN THIS CHAPTER 

♦ Creating tables and databases in MySQL 

♦ Choosing the proper column type and column attributes for tables 

♦ Maintaining databases and tables 



In Chapter 1 you learned that tables are the basis of all the good things that come 
from working with relational databases. There's a fair amount you can do with these 
tables, as you'll see throughout this book. So it should come as no surprise that the 
process of creating and maintaining the tables requires some knowledge. As Mom 
used to say, nothing good comes easy. 

If you're coming to MySQL from Microsoft's SQL Server or a desktop package 
like Access, you may be used to creating tables with a slick WYSIWYG (what you 
see is what you get) interface. In fact, there is a package called phpMyadmin that 
will give you many of the niceties you're used to working with. We use this tool 
and love it. We'll discuss it in further detail at the end of this chapter. There's no 
doubt that working with a graphical interface can be a lot more pleasant than fig- 
uring out the syntax of a language— any language. 



In fact, there are many GUI tools you can use when working with MySQL. See 
http: //www.mysql .com/downloads/contrib.html for a listing. The 
MySQL development team is working on an Access-like interface to MySQL. 
Check the mysql.com site for availability. 




21 



22 Part I: Working with MySQL 

However, even if you plan on installing and using this tool, you should take 
some time to learn how to create and maintain tables using the Data Definition 
Language (DDL), which is part of SQL. Specifically, it will be a great help to you to 
know the create and alter commands. Before too long you will have to use these 
commands within your scripts. There also may be an occasion when you don't have 
access to the graphical interface, and you'll need this knowledge to fall back on. 



Definitions 



Before we get to creating tables and databases in MySQL, there are a couple of 
items you'll need to understand. The concepts I'm about to present are very impor- 
tant—make sure you understand how to deal with these before you move forward 
in your database design. 

Null 

One of the first decisions you will have to make for every column in your tables is 
whether or not you are going to allow null values. If you remember back to your 
ninth grade math, you may recall the null set, which is a grouping that contains noth- 
ing. In relational databases, null has the same meaning: a null field contains nothing. 
Keep in mind that a null field is distinctly different from a text string with no 
characters (a zero-length string) or the numerical value of zero. The difference is 
that empty strings and zeros are values. In your programming you most likely have 
had an occasion where you have had to check whether a string contained any 
value, perhaps as part of an if . . . statement. In PHP, it would look like this: 

$var //this is a variable used in the test 

if ($var == "") 

{ 

echo "Var is an empty string"; 
) else ( 

echo $var; 
} 

The same syntax would work for comparing zero against another value. 

These sorts of comparisons will not work with null. Since null is the absence of 
value, any comparison with any value (including another null) is meaningless. In 
Chapter 3 you will see that null values require the application developer to be very 
careful when writing table joins. To give you a quick preview, consider what would 
happen if we wanted to join Table 2-1 and Table 2-2: 



Chapter 2: The Structured Query Language for Creating and Altering Tables 



23 




In your SQL select statements (covered in Chapter 3), there are a couple of 
ways you can check if afield contains a null value. First, you can use MySQL's 
i snul 1 ( ) function. For example to find rows in a table where the mid- 
dle_name column contains null values, you could runthefollowing query: 

select* from names where isnull(middle_name); 

Or, to exclude null values in the query result: 

select * from names where ! i snul 1 (middl e_name) ; 

The exclamation point means"not." 

You can also use the is null and is not nul 1 statements. For example: 

select * from users were addr2 is null; 
select * from users where addr2 is not null; 



Table 2-1 CONTACTS 



firstname 


lastname 


spouseid 


jay 


Greenspan 


1 


Brad 


Bulger 


NULL 



Table 2-2 SPOUSES 



spouseid 

1 



firstname 

Melissa 



lastname 

Ramirez 



If you wanted to find the authors of a great book on MySQL and PHP and their 
associated spouses, you would have to join these tables on the spouse_id field. 
(Don't worry if you don't understand the exact syntax, it will be covered in the next 
chapter.) 



SELECT * FROM Contacts, Spouses 

WHERE Contacts . spouse_i d = Spouses . spouse_i d 



24 Part I: Working with MySQL 

This statement would work fine for Jay, but there's going to be a problem for 
Brad because he's not married and his spouse_id field is null. He will not show up 
in the result set even though the goal of the query is to get all the people in the 
contacts table and the associated spouses if they exist. 

Again, this is just a preview, an example of why null is so important. In Chapter 3 
you will see how the outer join solves problems like this. 

Index 

Arguably the single greatest advantage of a relational database is the speed with 
which it can query and sort tremendous amounts of information. To achieve these 
great speeds, MySQL and all other database servers make use of optimized data- 
storage mechanisms called indexes. 

An index allows a database server to create a representation of a column that it 
can search with amazing speeds. Indexes are especially helpful in finding a single 
row or groups of rows from a large table. They can also speed up joins and aggre- 
gate functions, like min( ) and max( ), which we'll cover in Chapter 3. 

Given these advantages, why not just create an index for every column for every 
table? There are some very good reasons. First, indexes can actually slow some 
things down. It takes time for your database server to maintain indexes. You 
wouldn't want to create overhead for your server that is not going to be a benefit to 
you down the road. There are also occasions when the indexes themselves are 
slower. If you need to iterate through every row in a table, you're actually better off 
not using an index. Also, unnecessary indexes will use a lot of disk space. 

A table's primary key is often the subject of searches (for obvious reasons). Thus, 
in a table definition, the column or columns that you declare as your primary key 
will automatically be indexed. 

There will be more on creating indexes later in this chapter. 

create database Statement 

Before you can get to creating your tables, you'll need to create a database. This 
should take all of a second. The basic Create system is fairly simple and can be run 
from any interface that has access to MySQL. 
The general syntax is: 

create database database_name 



In case you're wondering, after running this command MySQL creates a 
folder in which it stores all the files needed for your database. On my Linux 
machine, the database folders are stored in /var/lib/mysql/. 





Chapter 2: The Structured Query Language for Creating and Altering Tables 25 

When naming databases, or for that matter columns or indexes, avoid using 
names that will cause confusion down the road. On operating systems where the file 
names are case sensitive, such as most Unix systems, database names will also be 
case sensitive. Come up with conventions that you plan on sticking to, such as using 
all lowercase names for tables and columns. Spaces are not allowed. Though MySQL 
can work around potentially bad choices, you should avoid using words that MySQL 
uses in the course of its business. For instance, naming a table "Select" is a really 
bad idea. In Chapter 7 of the MySQL reference manual, there is a list of over 150 
reserved words. If you stay away from words used by SQL or MySQL functions you 
should be OK. 

From the MySQL command line client, you can simply type in: 

create database database_name ; 



The MySQL command-line client is in the /bin directory of your MySQL 
installation and has the file name mysql (on Unix) or mysql.exe on 
DOS/Windows. 



From PHP, you can use either the mysql_create_db ( ) or the mysql_query( ) 

function. The following piece of code would create two databases. Keep in mind 
that you will need to be logged into MySQL as a user with the proper rights for the 
code to work. 

$conn = mysql_connect( "1 ocal host" , "username" , "password") 
or die ("Could not connect to 1 ocal host" ) ; 



mysql_creat e_d b ( " my _d a t a b a s e " ) or 

die ("Could not create database"); 

$string = "create database my_other_db" ; 

mysql_query($string) or 
die(mysql_error( ) ) ; 



use database Statement 

Before you can begin making tables in MySQL you must select a database that has 
been created. If you are accessing MySQL through the mysql command-line client, 
you will have to enter the statement: 

use database_name 



26 Part I: Working with MySQL 

If you're accessing a database through PHP, usethemysqi_seiect_dt>( ) function. 

$conn = mysql_connect( "1 ocal host" , "username" , "password") 
or die ("Could not connect to localhost"); 

mysql_sel ect_db( "test" , $conn) or 

die ("Could not select database"); 



create table Statement 

Once you have created and selected a database, you are ready to create a table. The 
basic Create Table system is fairly simple and takes this basic form. 

create table table_name 
( 

column_l col umn_type column attributes, 

column_2 col umn_type column attributes, 

primary key (col umn_name) , 

index i ndex_name(col umn_name) 



Column types, column attributes, and details on indexes are covered in the follow- 
ing sections. Before we get to those, there are two simplecolumn attributes to discuss: 

♦ null | not null 

♦ default 

The first gives you the opportunity to forbid or allow null values. If you don't 
specify "null" or "not null" it is assumed that null values are allowed. The second, 
if declared, sets a value if none is declared when you insert a row into the table. 

Here's an example create statement where you can see these two attributes, and 
a few others, put to use. This one was lifted from Chapter 12 and changed slightly. 

create table topics2 ( 

topi c_i d integer not null auto_i ncrement , 

parent_id integer default not null, 

root_id integer default 0, 

name varchar( 255) , 

description text null, 

create_dt timestamp, 

modify_dt timestamp, 



Chapter 2: The Structured Query Language for Creating and Altering Tables 27 

author varchar(255) null, 

author_host varchar(255) null, 
primary key (topi c_i d) , 
index my_i ndex(parent_i d) ) 

This statement creates a table named "topics" with nine columns and two 
indexes, one for the primary key and one for the parentjd column. In the above 
statement four column types are used: integer, varchar, text, and timestamp. These, 
and many other column types are discussed in further detail below. You should 
have a good understanding of all the column types available as well as ways to cre- 
ate indexes before you set out to create tables. 

To create tables from the command line client, key in the entire command. From 
PHP, use the mysql_query( ) function. 

$conn = mysql_connect( "1 ocal host" , "username" , "password") or 
die ("Could not connect to localhost"); 

mysql_sel ect_db( "test" , $conn) or 

die("could not select database"); 
$query = "create table my_table ( 

col_l int not null primary key, 

col_2 text 

mysql_query ( $query ) or 
d i e ( my s q 1 _e r r o r ( ) ) ; 



Column Types 



MySQL comes with a range of column types. Several are similar but have subtle yet 
important differences. Give this section a read and choose carefully when deciding 
on column types for your tables. 

Text column types 

MySQL has seven column types suitable for storing text strings: 

♦ char 

♦ varchar 

♦ tiny text 

♦ text 



28 Part I: Working with MySQL 

♦ mediumtext 

♦ longtext 

♦ enum 

CHAR 

Usage: char(length) 

The char column type has a maximum length of 255 characters. This is a fixed- 
length type, meaning that when a value is inserted that has fewer characters than 
the maximum length of the column, the field will be right-padded with spaces. So 
if a column has been defined as char(10) and you want to store the value "happy", 
MySQL will actually store "happy" and then five spaces. The spaces are removed 
from the result when the value is retrieved from the table. 

VARCHAR 

Usage: varchar(length) 

This is nearly identical to char and is used in many of the same places. It also 
has a maximum length of 255. The difference is that varchar is a variable-length 
column type. The values will not be padded with spaces. Instead MySQL will add 
one character to each varchar field, which stores the length of the field. MySQL 
removes spaces from the end of strings in varchar fields. 

USING CHAR OR VARCHAR For the most part there is little practical difference 
between char and varchar. Which one you decide to use will depend on which will 
require more space, the trailing spaces in a char column or the single character in 
varchar. If your field stores something like last names, you'll probably want to allow 
25 characters, just to be safe. If you were to use the char column type and someone 
had the last name Smith, your column would contain 20 trailing spaces. There's no 
need for it; you're much better off using varchar and allowing MySQL to track the 
size of the column. However, when you want to store passwords of five to seven 
characters, it would be a waste to use varchar to track the size of the column. Every 
time a varchar field is updated, MySQL has to check the length of the field and 
change the character that stores the field length. You'd be better off using char(7). 



If you define a column as varchar with a column length of less than four, 
MySQL will automatically change the column to the char type. 




Chapter 2: The Structured Query Language for Creating and Altering Tables 29 

TINYTEXT 

Usage: tinytext 

This is first of the four binary (or blob) text character types. All of these types 
(tinytext, text, mediumtext, and largetext) are variable column types, similar to 
varchar. They differ only in the size of string they can contain. The tinytext type 
has a maximum length of 255, so in fact it serves the same purpose as varchar(255). 
An index can be created for an entire tinytext column 

TEXT 

Usage: text 

The text type has a maximum length of 65,535 characters. Indexes can be created 
on the first 255 characters of a text column. 

MEDIUMTEXT 

Usage: medi umtext 

The mediumtext type has a maximum length of 16,777,215 characters. Indexes 
can be created on the first 255 characters of a mediumtext column. 

LONGTEXT 

Usage: longtext 

The longtext type has a maximum length of 4,294,967,295 characters. Indexes 
can be created on the first 255 characters of a longtext column. However, this col- 
umn currently is not very useful, as MySQL allows string of only 16 million bytes. 

ENUM 

Usage: en um ('valuel', 'value2', 'value3' ...) [default 'value'] 

With enum, you can limit the potential values of a column to those you specify. 
It allows for 65,535 values, though it's difficult to see a situation where you'd want 
to use this column with more than a few potential values. This type would be of use 
when, for example, you want to allow only values of "yes" or "no". The create 
statement that makes use of enum will look like this: 

create table my_table ( 

id int auto_i ncrement primary key, 
answer enum ('yes', 'no') default 'no' 



SET Usage: set ( 'val uel ' , 'value2', 'value3' ...) [default 'value'] 

This column type defines a superset of values. This allows for zero or more val- 
ues from the list you specify to be included in a field. 



30 Part I: Working with MySQL 

You will not see this column type used in this book. We do not like to see multi- 
ple values in a single field as it violates very basic rules of database design. Re-read 
Chapter 1 if you don't know what this means. 

Numeric column types 

MySQL has seven column types suitable for storing numeric values. Note that the 
following are synonyms: int and integer; double, double precision, and real; and 
decimal and numeric. 

♦ int/integer 

♦ tinyint 

♦ mediumint 

♦ bigint 

♦ float 

♦ double/double precision/real 

♦ decimal/numeric 

For all numeric types the maximum display size is 255. For most numeric types 
you will have the option to zerofill a column— to left- pad it with zeros. For example, 
if you have an int column that has a display size of 10 and you insert a value of 25 
into this column, MySQL will store and display 0000000025. The numeric column 
types may also be defined as signed or unsigned. Signed is the default definition. 

INT/INTEGER 

Usage: i nt(di spl ay size) [unsigned] [zerofill] 

If you use the unsigned flag, this column type can store integers from to 
4,294,967,295. If signed, the range is from -2,147,483,648 to 2,147,483,647. Int 
will often be used with auto_ increment to define the primary key of a table. 

create table my_table ( 

table_id int unsigned auto_i ncrement primary key, 

next_column text 
); 

Note that I've used an unsigned column because an autoj ncrement column has 
no need for negative values. 

TINYINT 

Usage: ti nyi nt(di spl ay size) [unsigned] [zerofill] 

If unsigned, tinyint stores integers between and 255. If signed, the range is 
from -128 to 127. 



Chapter 2: The Structured Query Language for Creating and Altering Tables 31 

MEDIUMINT 

Usage: medi umi nt(di spl ay size) [unsigned] [zerofill] 

If unsigned, mediumint stores integers between -8,388,608 and 8,388,607. If 
signed, the range is from to 1677215. 

BIGINT 

Usage: bi gi nt(di spl ay size) [unsigned] [zerofill] 

If unsigned, bigint stores integers between -9,223,372,036,854,775,808 to 
9,223,372,036,854,775,807. If signed, the range is from to 18,446,744,073,709, 
551,615. 

FLOAT 

Usage: FLOAT(precision) [zerofill] 

In this usage, float stores a floating-point number and cannot be unsigned. The 
precision attribute can be <=24 for a single-precision floating-point number and 
between 25 and 53 for a double- precision floating-point number. Starting in MySQL 
3.23, this is a true floating-point value. In earlier MySQL versions, FLOAT(precision) 
always has two decimals. 

Usage: float[(m,d)] [zerofill] 

This is a small (single- precision) floating-point number and cannot be unsigned. 
Allowable values are -3.402823466E+38 to -1.175494351E-38, zero, and 1.175494 
351E-38 to 3.402823466E+38. M is the display width and D is the number of deci- 
mals. FLOAT without an argument or with an argument of <= 24 stands for a 
single-precision floating-point number. 

DOUBLE/DOUBLE PRECISION/REAL 

Usage: D0UBLE[(M,D)] [zerofill] 

This is a double-precision floating-point number and cannot be unsigned. 
Allowable values are -1.7976931348623157E+308 to -2.2250738585072014E-308, 
zero, and 2.2250738585072014E-308 to 1.7976931348623157E+308. M is the dis- 
play width and D is the number of decimals. 

Usage: decimal[(M[ ,D])] [zerofill] 

Numbers in a decimal column are stored as characters. Each number is stored as 
a string, with one character for each digit of the value. If D isO, values will have no 
decimal point. The maximum range of DECIMAL values is the same as for DOUBLE, 
but the actual range for a given DECIMAL. If M is left out, it's set to 10. 

Date and time types 

MySQL has five column types suitable for storing dates and times. 



date 


♦ time 


datetime 


♦ year 


timestamp 





32 



Part I: Working with MySQL 



MySQL date and time types are flexible, accepting either strings or numbers as 
part of insert statements. Additionally, MySQL is pretty good at interpreting dates 
that you give it. For instance, if we create this table: 

create table date_test( 

id int unsigned auto_i ncrement , 
the_date date 



The following insert statements are all interpreted correctly by MySQL: 

insert into date_test (a_date) values ('00-06-01'); 
insert into date_test (a_date) values ('2000-06-01'); 
insert into date_test (a_date) values ('20000601'); 
insert into test6 (a_date) values (000601); 




MySQL prefers to receive dates as strings. So '000601' is a better choice than 
a similar integer. Using strings for date values may save you from encounter- 
ing some errors down the road. 



Extracting information from date and time columns can be a challenge. MySQL 
provides many functions that help manipulate these columns. 

DATE 

Usage: date 

The date column type stores values in the format YYYY-MM-DD. It will allow 
values between 1000-01-01 and 9999-12-31. 



DATETIME 

Usage: dateti me [null | not null] [default] 

The datetime type stores values in the format: YYYY-M M -DD HH:M M :SS. 
allow values between 1000-01-01 00:00:00 and 9999-12-31 23:59:59. 



Itwil 



TIMESTAMP 

Usage: timestamp(size) 

This is a handy column type that will automatically record the time of the most 
recent change to a row, whether it is an insert or an update. Size can be defined as 
any number between 2 and 14. Table 2-3 shows the values stored with each column 
size. The default value is 14. 



Chapter 2: The Structured Query Language for Creating and Altering Tables 33 



Table 2-3 TIM ESTAMP FORMATS 



Size 


Format 


2 


YY 


4 


YYMM 


6 


YYM M DD 


8 


YYYYM M DD 


10 


YYMMDDHHMM 


12 


YYMMDDHHMMSS 


14 


YYYYM MDDHHMMSS 



TIME 

Usage: time 

Stores time in HH:MM:SS format and has a value range from -838:59:59 to 
838:59:59. The reason for the large values is that the time column type can be used 
to store the result of mathematical equations involving times. 

YEAR 

Usage: year[(2|4)] 

In these post-Y2K days it's hard to imagine that you'd want to store your years 
in two-digit format, but you can. In two-digit format, allowable dates are between 
1970 and 2069. The digits 70-99 are prepended with 19 and 01-69 are prepended 
with 20. 

Four-digit year format allows values from 1901 to 2155. 



Creating Indexes 



Starting in version 3.23.6 MySQL can create an index on any column. There can be 
a maximum of 16 columns for any table. The basic syntax is: 

index index_name ( i ndexed_col umn) 



34 



Part I: Working with MySQL 




Although the index name is optional, you should always name your indexes. 
It becomes very important should you want to delete or change your index 
using the SQL al ter statement. If you don't specify a name, MySQL will 
base the index name on the first column in your index. 



Another way to create an index is to declare a column as a primary key. Note 
that any autojncrement column must be indexed, and you'll probably want to 
declare it as your primary key. In the following table, the idcol column is indexed. 

create table my_table ( 

id_col int unsigned auto_i ncrement primary key, 
another col text 



The primary key can also be declared like other indexes after the column defini- 
tions. 

create table my_table ( 

id_col int unsigned not null auto_i ncrement , 
another_col text, 
primary key ( id_col ) 



Indexes can span more than one row. If a query uses two rows in concert during 
a search, you could create an index that covers the two with this statement: 

create table mytable( 

id_col int unsigned not null, 

another_col char(200) not null, 

index dual_col_index(id_col , another_col ) 



This index will be used for searches on idcol and another_col. These indexes 
work from left to right. So this index will be used for searches that are exclusively 
on idcol . However, it will not be used for searches on another_col. 

Finally, you can create indexes on only part of a column. Starting in MySQL ver- 
sion 3.23 you can index tinytext, text, mediumtext, and longtext columns on the 
initial 255 characters. For char and varchar columns, you can create indexes for 
the initial portion of a column. Here the syntax is: 



index index_name (col umn_name(col umn_l ength) ) 



Chapter 2: The Structured Query Language for Creating and Altering Tables 35 

For example: 

create table my_table( 

char_column char (255) not null, 
text_col umn text not null, 
index i ndex_on_char (char_col umn(20) ) , 
index i ndex_on_text ( text_col umn (200) ) 



An index can also assure that unique values exist in every row in a table by 
using the unique constraint. 

create table my_table( 

char_column char (255) not null, 

text_col umn text not null, 

unique index i ndex_on_char (char_col umn ) 
); 



Table Types 



MySQL offers three table types: ISAM, MylSAM, BDB, and Heap. ISAM is an older 
table type and is not recommended for new applications. The default table type is 
MylSAM. The syntax for declaring a table type is 

create table table_name type=tabl e_type( 

col_name column attribute 
); 

MylSAM tables are extremely fast and very stable. There's no need to declare 
another table type unless one of the other table type fits your specific needs. 

Heaps are actually memory- resident hash tables. They are not stored in any 
physical location and therefore will disappear in the case of crash or power outage. 
But because of their nature, they are blazingly fast. You should only use these for 
temporary tables. 

Starting in MySQL version 3.23.16, MySQL offers BDB tables. These tables are 
transaction safe and are integral to MySQL's efforts to include transactions. Check 
Section 8.4 of the MySQL reference manual to see the current state of BDB tables. 



alter table Statement 



If you're not happy with the form of your table, you can modify it with the alter 
table statement. Specifically, this statement allows you to rename tables, columns, 



36 



Part I: Working with MySQL 



and indexes; add or drop columns and indexes; and redefine the definitions of 
columns and indexes. This statement will always start with alter table table_name. 
The rest of the command will depend on the action needed as described below. 

Changing a table name 

The syntax for changing a table name is as follows: 

alter table table_name rename new_tabl e_name 



If you have MySQL version 3.23.27 or higher you can make use of the 
rename statement.The basic syntax is 

rename table_name to new_tabl e_name 




Adding and dropping columns 

When adding a column, you will need to include all the column definitions defined 
in the previous section. In addition, starting in version 3.22, MySQL allows you to 
specify where in the table the column will reside, although this specification is 
optional. The basic syntax is: 

alter table table_name add column column_name column attributes 

For example: 

alter table my_table add column my_col umn text not null 

To specify the location of the column, use fi rst to specify your inserted column 
as the first column in the table or after to place the column following a column that 
already exists, as shown in the following examples. 

alter table my_table add column my_next_col text not null first 
alter table my_table add column my_next_col text not null after 
my_other_col umn 

To drop a column, you need only the following: 

alter table tablejame drop column column name 



Chapter 2: The Structured Query Language for Creating and Altering Tables 37 




When altering a table, try to get all of your changes into a single alter state- 
ment. It's better practice than, for example, deleting an index in one state- 
ment and creating a new one in another statement. 



Adding and dropping indexes 



You can add indexes using the index, unique, and primary key commands in the 
same way they are used in the create statement. 

alter table my_table add index index_name ( col umn_namel , 
col umn_name2 , . . . ) 

alter table my_table add unique i ndex_name( col umn_name) 
alter table my_table add primary key (my_col umn ) 

Making your indexes go away is just as easy with the drop command. 

alter table table_name drop index index_name 
alter table_name testlO drop primary key 



Changing column definitions 



It is possible to change a column's name or attributes with either the change or 
modify commands. To change a column's name, you must also redefine the col- 
umn's attributes. The following will work: 

alter table table_name change ori gi nal_col umn_name new_col umn_name 
int not null 

But this will not: 

alter table table_name change my_col 2 my_co!3; 

If you wish to change only the column's attributes, you can use the change com- 
mand and make the new column name the same as the old column name. For 
instance, to change the column col_l from a char(200) column to a varchar(200) 
column, you could use the following: 

alter table table_name change col_l col_l varchar(200) 

Starting in MySQL version 3.22.16, you could also use the modify command. 

alter table table_name modify 1 col_l varchar(200) 



38 Part I: Working with MySQL 



insert Statement 



Now that you know all you need to know about creating and modifying tables, 
you're probably going to want to put some information into the table. You do this 
by using the insert statement. 

The basic form of the SQL insert statement is: 

insert into table_name (column_l, column2, col umn3, . . . ) values 
(valuel, value2, value3 ...) 

If a column in your table allows null values, you can leave it out of the insert 
statement. 

Text strings must be surrounded by single quote marks (')■ For example: 

insert into table_name (text_col, int_col) values ('hello world', 1) 

This can cause a problem because undoubtedly someone is going to want to 
insert a contraction into your database and that would confuse your database. 
Therefore you'll need a way of escaping the single quote character. In fact there are 
several characters that need to be escaped in order to be inserted into MySQL. If 
you want to insert any of the following characters into a text field they must be 
prepended with a backslash: 

♦ ' (single quote) 

♦ " (double quote) 

♦ \ (backslash) 

♦ % (percent sign) 

♦ _ (underscore) 

You can also escape single quotes by using two consecutive single quote marks 

C). 

The following characters are identified in MySQL by their typical escape sequences: 

♦ \n (newline) 

♦ \t (tab) 

♦ \r (carriage return) 

♦ \b (back space) 

It's worth noting here that, for the most part, you won't have to worry about 
escaping all of these characters while doing your PHP programming. As we'll see 



Chapter 2: The Structured Query Language for Creating and Altering Tables 39 

there are functions and settings built into PHP that handle this automatically. The 
addslashes( ) function and the magic quotes settings in the php.ini are particu- 
larly helpful. 



update Statement 



The SQL update statement is slightly different from the others we've seen so far, in 
that it makes use of a where clause. The general syntax is: 

update table_name set col_l = val uel , col_2=val ue_2 where col=value 

Once again, if you're inserting a string, you'll need to surround it with single 
quotes and escape properly. Keep in mind that the where portion of the update 
statement can be about any comparison operator. Often it will be used to identify a 
single row by its primary key. In Table 2-4, id is the primary key. 



Table 2-4 FOLKS TABLE 



id 


fname 


Iname 


salary 


1 


Don 


Ho 


25,000 


2 


Don 


Corleone 


800,000 


3 


Don 


Juan 


32,000 


4 


Don 


Johnson 


44,500 



This statement would affect only Don Corleone: 

update folks set fname='Vito' where id=2 

As you can see, it would be risky to run an update statement based on the 
fname column, as you could accidentally update every column in this table. 

update folks set fname='Vito' where fname='don' 

You could also use update to give your underpaid employees a raise: 

update folks set salary=50000 where sal ary<50 ,000 



40 



Part I: Working with MySQL 



drop table/drop database 

If you wish to get rid of a table or an entire database, the drop command will do 
the trick. Keep in mind that if you drop a database you will lose all of the tables 
that exist within the database. 

drop table table_name 

drop database database_name 

The drop table command can be from PHP through the mysql_query( ) func- 
tion. If you wish to drop a database from PHP, you need to make use of the 

mysql_drop_db( ) function. 



show tables 

To get a list of the tables available in a database, use the show tabi es command. 
For this command to work, you must have already selected a database using the 

use database command. 

Figure 2-1 shows the response to the show tables command from the MySQL 
command line client. 



] Linux Box - CRT 



File Edit View Options Transfer Script Window Help 



% e q | g 1% a | & m 



mysql> show tables; 



I Tables in test I 



companies 

contacts 

contributions 

rny_new_table 

niymine_table 

my table 

rnytable2 

sessions 

show_stuff 

tablel 

test 10 

test3 

test4 

testE5 

testG 

testS 

topics 

topics_wow 

user_ages 

users 



20 rows in set (0 + 00 sec) 



Ready 
liQ Start I' 



iTelnet 



"I 27, 8 | 27 Rows, 86 Cols [VT100 



[nUm A 



*«%^<MdKsWS 7:35 AM 



Figure 2-1: The show tables command from the MySQL command line client 



Chapter 2: The Structured Query Language for Creating and Altering Tables 



41 



From PHP, you can get a list of tables by using mysql_l i st_tabl es( ). 

<? 

mysql_connect( "1 ocal host" , "root", ""); 

$ result = mysql_l i st_tabl es( "test" ) ; 

while($row = mysql_fetch_array($resul t) ) 
t 

echo $row[0] . "<br>\n"; 
} 
?> 




You are better off not using mysql_list_tables(), as this function may not be 
available in the future. Running a show tables command through mysql_ 
queryf) is a better choice. 



show columns/ show fields 

These commands, which are synonymous, are very handy if you can't remember the 
column types and attributes you declared in your create statement. For example, 
let's say you created a table with the following command. 



create table topics ( 
topi c_i d 
parent_id 
root_i d 
name 

descri pti or 
create_dt 
modi fy_dt 
author 
author_host 



integer not null auto_i ncrement primary key, 

integer default not null, 

integer default 0, 

varchar(255) , 

text null, 

times tamp , 

times tamp , 

varchar(255) null, 

varchar(255) null, 



index my_i ndex(parent_i d) 



) 



Figure 2-2 shows the results you would get after running show fields from 
topics from the MySQL command line client. 



42 



Part I: Working with MySQL 



File Edit View Options Transfer Script Window Help 



ma 



Brieai^HQics m | ^ m I $ 



nysql> shoLii Fields from history^ 



T3 



1 Field 


Type 


Hull 


Key 


Default 


Extra 


1 entry id 


int(ll) 




PRI 





auto increment 


1 problem id 


int(ll) 




HUL 







1 entry type id 


tinyint(4) 











1 entered by 


uarchai-(20> 


YES 




NULL 




1 source id 


tinyintf 4) 











1 entry dt 


timestamp(14) 


YES 




HULL 




1 notes 


text 


YES 




HULL 





7 rows in set (0.03 sec! 

roysql> | 

Ready 



| 36, | 16 Rows, 85 Cols VT100 



|NUM J, 



Figure 2-2: The show fields command from the MySQL command line client 

You can get similar information through the PHP interface by using mysqi_ 

fielcLname( ), mysql_f i el d_type( ), and mysql_f i el d_l en( ). All of the syntax 

and functions in this code are covered in Part II of this book. 

$db = mysql_connect( "1 ocal host" , "root" , "") 

or die ("Could not connect to localhost"); 
mysql_sel ect_db( "test" , $db) 

or die ("Could not find test"); 

$db_name ="topi cs" ; 
Iquery = "select * from ldb_name"; 
Iresult = mysql_query ( $query ) ; 
$num_fields = mysql_num_f i el ds( $resul t) ; 

//create table header 

echo "<table border = 1 > " ; 

echo " < t r > " ; 

for ($i=0; $i<$num_fields ; $i++) 

{ 

echo " < t h > " ; 

echo mysql_f i el d_name (Iresult, $i); 

echo "</th>"; 
) 

echo "</tr>"; 
//end table header 

//create table body 

echo " < t r > " ; 

for ($i=0; $i<$num_fields ; $i++) 



echo "<td valign = top>"; 



Chapter 2: The Structured Query Language for Creating and Altering Tables 



43 



echo mysql_f i el d_type ($result, $i) . "<br> \n"; 

echo "(" . mysql_f i el d_l en ($ result, $i) . ")<br> \n' 

echo mysql_f i el d_f 1 ags ($ result, $i) . "<br> \n"; 

echo "</td>"; 
} 

echo "</tr>"; 
//end table body 

echo "</table>"; 



Using phpMyAdmin 

If you are an old-time Unix guy or gal, you may be perfectly comfortable keying in 
all of your commands and sorting out the errors when you mistype something or 
screw up the syntax. But myself, I like the convenience of a graphical interface. 

If you're like me, you will be thrilled to know that a couple of programmers have 
used PHP to create a great Web- based interface to MySQL. Best of all, they're giv- 
ing their program away. All you need to do is cruise over to phpwizard.net and 
download the appropriate .tar, .gz, or .zip file. We would have included it on the 
CD, but you're really better off getting the most recent version off their site. It's 
constantly being improved. 

This package will cover about all the MySQL administrative functions you'll come 
across. Figures 2-3 and 2-4 show a bit of what you can expect for phpMyAdmin. 



J File Edit View Favorites Tools Help 




*> , ■*■ . © a a 


© a ® 


b- a m , 




Back Forward Stop Refresh Home 


Search Favoriles History 


Mail Print Edit 




Address |S] http://192.1Gai. l/mysqladmm/phpMyAdmin/index.phpa _-J jt>G 


j |J Links » 


Home 
IS 


Welcome to phpMyAdmin 2.0.5 




database name 






B mysql 


MySQL 3.22.27 -log running on localhost 




db 
func 
host 
tables priv 


• <] ir il~ ■■ r . ■ I ■ L- i : t i'.-.'jin- | 






| Create 




user 


* Show MySQL runtime information [Documentation] 




B test 


■ Show MySQL system variables [Documentation] 




companies 


• lli" --■.■.- . .'.'J ii r 1 1 - ■ ■ ■ | 




contacts 


. i -I -.i r. - i |i ■■■ . ■ | 




mytable 


■ phpMyAdrnin-Homepage 




mytable2 


• phpMyAdmin Documentation 




tablel 






testlO 






test3 






test4 






tests 






teste 






tests 






topics 






Mstart||j 4i *> © ■ & *$ I'^j^ijjHyi^i ^j| j ||^>^ C $ D %^ xj^% 


10.41 AM 



Figure 2- 3: View of phpMyAdmin 



44 



Part I: Working with MySQL 



I |.|,I,U,HJ.|„II,B II..,U,„II,U.,LH'|.IMH1 



File Edit View Favorites Tools Help 



Back Forward Stop Refresh Her 



I a 

rch Favorites History 



Mail Print 



Address [Tj hltp^/l 92.1GG 1 Umysqladmi^hpMyAdmiiVindex.php3 



~B && 



5H 

|J Links 



daf:3hase_narne 
S rnysql 



host 
tables_priv 

B test 

companies 

mytable 

mytableZ 

tablel 

testlO 

test3 

test4 

tests 

tests 

testa 



Database test - table companies 

Select fields (at leasl one): 



■ Add search conditions (body of the "where" clause): 
I [Documentation] 

■ Do a "query by example" (wildcard: "%") 

Field Type Value 

I 



company_id 
company_name string | 



cornpany_address string | 



ashiit| e ii *> w m '^ i$ wi^}f2}m\\^j ta\%\ 



•S^hJ&i-O-i -■■&<>*, 10.40AM 



Figure 2-4: Another View of phpMyAdmin 



Summary 



This chapter discussed everything you need to know in order to create and main- 
tain databases and database tables when working with MySQL. It is possible that 
you will never need to commit the details of the create statement to memory, as 
graphical tools like phpMyAdmin can help you create and alter tables. 

Still, it is important to understand the column types and the purposes of indexes, 
as a quick and efficient database will always use the correct data type and will only 
include indexes when necessary. As you will see in Parts III and IV of this book, we 
take a good deal of care to make sure that we get the most out of our databases. 



Chapter 3 

Getting What You 
Want with select 

IN THIS CHAPTER 

♦ Understanding the basics of the SQL sel ect statement 

♦ Working with the where and from clauses 

♦ Joining two or more tables 

♦ Learning the non-supported aspects of the select syntax in MySQL 



The select statement is your key to getting exactly what you want from your 
database. It's amazingly agile, capable of getting about any set of data that you can 
imagine. Working with sel ect can get a bit hairy when queries get extremely com- 
plex, but once you understand the basics, which are covered in this chapter, you 
should be able to get almost any group of data from your database. 



There are very good books that spend a long time explaining the details of 
the sel ect statement. If you find that this chapter doesn't cover something 
you need, we suggest you first turn to the MySQL manual, and then the 
MySQL mailing lists. In addition, SQL For Dummies, also published by IDG 
Books Worldwide,covers the ANSI standard in some pretty good detail. 




Basic select 



When it comes time to take the information from your database and lay it out on 
your Web pages, you'll need to limit the information returned from your tables and 
join tables together to get the proper information. So you'll start with your data- 
base, the superset of information, and return a smaller set. In the sel ect statement 
you'll choose columns from one or more tables to assemble a result set. This result 
has columns and rows and thus can be effectively thought of as a table (or a two- 
dimensional array, if your mind works that way). This table doesn't actually exist in 
the database, but it helps to think about it this way. 45 



46 Part I: Working with MySQL 

The basic sel ect statement requires you to indicate the table you are selecting 
from and the column names you require. If you wish to select all the columns from 
a given table, you can substitute an asterisk (*) for the field names. 

select column_l, column_2, col umn_3 from table_name 

or 

select * from table_name 

Keep in mind that with a select statement you are not actually altering the 
tables involved in the query. You are simply retrieving information. From PHP, you 
will send the query to MySQL from the mysql_query( ) function. 

There are all sorts of ways you can choose to lay out the information, but at 
times you're going to want a simple HTML table with the column names put in a 
header row. The simple PHP code in Listing 3-1 will lay out any SQL query in an 
ultra-simple HTML table. It includes a simple form that will allow you to enter a 
query. If you don't understand this code just yet, don't worry about it; all the PHP 
functions will be covered in Chapter 6. Alter the mysql_connect( ) and mysql_ 
seiect_db( ) functions if you wish to change the database used. 

Listing 3-1: PHP script that converts an SQL query to an HTML table 

<?php 

i f ( ! i sset( $query ) || empty ( $query) ) 

(Iquery = "select * from users";} 
//stri psl ashes is necessary because the select statement is 
//coming from a form. In most systems, the magi c_quotes 
//setting (see Appendix B) will prepend single quotes 
//with backslashes, which could be problematic. 
$query=stri psl ashes (Iquery) ; 

mysql_connect( "username" , "password", "") 

or d i e ( " Could not connect to database."); 
mysql_sel ect_db( "test" ) or 

die("Cannot select database"); 
Iresult = mysql_query( Iquery ) or 

di e( mysql_error( ) ) ; 

$number_cols = mysql_num_f i el ds( Iresul t ) ; 

echo "<b>query: $query</b>"; 
//layout table header 
echo "<table border = 1 > \ n " ; 
echo "<tr al i gn = center>\n" ; 



Chapter 3: Getting What You Want with select 47 



for ($i=0; $i<$number_cols; $i++) 
t 

echo "<th>" . mysql_f i el d_name( $resul t , $i). "</th>\n"; 
} 
echo "</tr>\n";//end table header 

//layout table body 

while ($row = mysql_fetch_row( $resul t ) ) 

{ 

echo "<tr al i gn=l eft>\n" ; 

for ($i=0; $i <$number_col s ; $i++) 

{ 

echo " < t d > " ; 

if ( ! isset($row[$i ] ) ) //test for null value 

(echo "NULL";} 
el se 

(echo $row[$i ] ; } 
echo "</td>\n"; 
} 
echo "</tr>\n"; 



echo "</table>"; 
?> 

<form action="<? echo $PHP_SELF?>" method="get"> 

<input type="text" name="query" si ze="50"Xbr> 

<input type="submi t"> 
</f orm> 

For the remainder of this chapter you will see how to build on the complexity of 
the select statement. To see things in action, we created a table in MySQL against 
which we can run these queries. This is the create statement for a table named 
"users", which holds basic personal information: 

CREATE TABLE users ( 

userid int(10) unsigned NOT NULL auto_i ncrement , 

fname varchar(25) NOT NULL, 

Iname varchar(25) NOT NULL, 

addr varchar(255) NOT NULL, 

addr2 varchar(255) , 

city varchar(40) NOT NULL, 

state char(2) NOT NULL, 

zip varchar(5) NOT NULL, 



48 



Part I: Working with MySQL 



lastchanged timestamp( 14) , 

PRIMARY KEY (userid) 

); 

To get things started, we loaded up the database with a few rows of information. 
When run through the PHP code above, the query select * from users will 
return the results shown in Figure 3-1. 



^http://192.168.1.1/book/ch3 functions.php?query^select+*+ 


From 


■-users 


Microsoft Intern... 1SQ 


J Eile 


Edit View Favorites lools 


Help 






m 


J Address |*S] http://1 92.168.1. 1/book/ch3Junctions.php?query=select+"+from+users 


jj (f^Go 


J Links » 




query 


select * from users 










^J 




useiit 


| fiiame | blame | addr 


| addr2 | city 


state 


1 *? 


| lastchanged 






1 


Jason Greenspan 555 5th St 


|apt204 ■SanFrancisco 


|CA 


|S4118 


|2000061414293° 






2 


|Brad |Bulger |o"6"6" 6th St 


|apt 17 |San Francisco 


|CA 


|94116" 


|20000614142939 


3 


,|john |Doe |279 66th St 


|NULL|NewYork 


Iny 


|l 1 100 


|20000614143030 


4 


Ijane |Doe |°87 67thSt 


|NULL |windsor 


|ma 


|l4102 


|20000614143019 


5 


|jean |Banks |4ElmSt 


Eugene 


|OE 


|S8712 


20000614143037 


& 


ponny Alphonse 25 14th St 


'NULLNevYork 


Iny 


|lll04 


|20000614143030 


7 


Meghan Garcia 44MapleDr 


[NULL (Nashville 


Itn 


J37114 


120000614143132 


8 


Kenny Clarke General Devliver pSTULL Washeegan 


Ivt 


|l0048 


|20000614143200 


9 


iDanny iBriggs ;8 Palm Way 


ste 222 Miami 


Ifl 


NULL |20000614143 119 


10 


Luke Gnome 8 Palm Way 


NULL; San Francisco 


|CA 


|°4118 


,20000614142939 


11 


Alan Paine 27CasaWay 


NULL Los Angeles 


|CA 


|S4204 


|200006 141 44723 


12 


|jay |Grimes |718 Field St 


|NULL |Pierre 


|nd 


J44221 


|20000613103941 


























Submit Query 1 




JiBStartl g^V^m^^ 


lie »l tjiiw* 


)0 ( $'%0-j $)S V-ojO 8:29 AM 



Figure 3-1: Results of query using select * from users 

The where clause 

The where clause limits the rows that are returned from your query. To get a single 
row from a table, you would a run the query against the primary key. For instance, 
to get all the information on Brad, you would use this query: 

select * from users where userid = 2 

Figure 3-2 shows the results of this query. 

If you're doing a comparison to a column that stores a string (char, varchar, etc), 
you will need to surround the string used for comparison in the where clause by 
single quotes. 



select * from users where city = 'San Francisco' 



Chapter 3: Getting What You Want with select 



49 



3 http://192.168. 1.1/book/ch3 functions.php?query^select+*+from+users+where+userid+=... HElES 



File Edit View Favorites lools Help 



Address |g] n 92.1 G8.1 .1 /book/ch3_functions.php?querv=select+"+from+users+where+userid+K3D+2j^J j^Go 



Links : 



<^ T ^ T @ L3 (ft I a Sa 

Back Stop Refresh Home Search Favorites History 



Print 



Edit 



query: select * from users where us end = 2 



~2 



use rid frame biame | addr |addr2 | city |state | zip | lastchanged 




|2 |Brad |Bulger |6GG 6th St | apt 17 |S an Francisco |c A |941 16 |20000o"141 42939 








1 Submit Query 1 



aastarti m«ji<*®m&mi 1 1 aco... n^htt... ||Jau>»'ff^o-j <§^q> 10:27 am 

Figure 3- 2: Results of query using select * from users where userid=2 



MySQL has several comparison operators that can be used in the where clause. 
Table 3-1 lists these operators. 



Table 3-1 MYSQL COMPARISON OPERATORS 



Operator Definition 

equal to 

<>or!= not equal to 

< less than 

<= less than or equal to 

> greater than 

>= greater than or equal to 

Like Compares a string (discussed in detail later in this Chapter) 



50 Part I: Working with MySQL 



You can compare several operators at once by adding and or or to the where 
clause. 

select * from users 
where userid = 1 or 

city = 'San Francisco' 

select * from users 
where state = 'CA' and 

city = 'San Francisco' 

It's important to note that fields with null values cannot be compared with any 
of the operators used in Table 3-1. For instance, in the table shown in Figure 3-1, 
you might think that the following statement would return every row in the table: 

select * from users where zip <> '11111' or state = '11111' 

But in fact, row 9 will not be returned by the query. Null values will test neither 
true nor false to any of these operators. Instead, to deal with null values, you will 
need to make use of the is null oris not null predicates. 

To get the previous query to work as we had intended you'd need to augment 
your original query. 

select * from users 
where zip <> '11111' or 

zip = '11111' or 

zip is null 

Or if you wanted to find all the rows where the zip contained any value (except 
null) you could use the following: 

select * from table where zip is not null 

USING DISTINCT 

There will betimes where your query will contain superfluous data. For instance, if 
your goal was to see all the cities in California, your first instinct might be to run a 
query like select city, state from users where state= ' CA' . But look at the 
result returned in Figure 3-3. 

Notice that the first three rows are identical. This is no good, as there's no need 
for these extra rows. You can get by this by using select distinct. When you use 
distinct, the MySQL engine will remove rows with identical results. So here the 
better query is sel ect distinct city, state from users where state='CA', 
which returns the data in Figure 3-4, which is exactly what you want. 



Chapter 3: Getting What You Want with select 



51 



^http://192.168.1.1/book/ch3 functions.php?query^select+citv,+state+from+users+where 



File Edit View Favorites lools Help 



ID 



Address |g] ch3Junctions.php?querji=select+citji^2C+state+from+users+where+state«3D^27CA^27_^J i^Go 




Back Forward Stop 



Refresh Home 



© a 

Search Favorites History 



Print 



Edit 



query: select city, 


city 


state 


San Francisco 


|CA 


|S an Francisco 


|CA 


San Francisco 


|CA 


|Los Angeles 


|CA 



state from users where state='CA' 



Submit Query 



IgStartj | e, *> ffl ■ & a® \\WMJ\^h. 



°'%>>yJ~i<§S*><„D 8:55 AM 



Figure 3- 3: Results of query using select city, state from users where state='CA' 



3 http://192.168. 1.1/book/ch3 fu n cti o ns. p h p? q u e ry=se I ect+distinct+ city, +state+from+ users... HElEa 



File Edit View Favorites lools Help 



Address |@ ctions.php?querji=select+distinct+citji^2C+state+from+users+where+stateX3DS;27CA^27_^J i^Go 

is- & i 

Mail Print E 



^ T =*>.@ E ffl I © Si 

Back Stop Refresh Home Search Favorites History 



query: select distinct city, state from users where state='CA' 



I 


city 


state 


Los 


Angeles 


|CA 


San 


Francisco 


|CA 



Submit Query 



J 



lastartl ft, <D rjj ■ 'A J3 _^|[glfflj t% 1 a,)o t ff'X>J<f^^>P 9:02AM 



Figure 3-4: Results of query using select distinct city, state from users where state='CA' 



52 Part I: Working with MySQL 

USING BETWEEN 

You can also choose values within a range by using the between predicate. 
between works for numeric values as well as dates. In the following query, 
lastchanged is a timestamp column. If you wanted to find the people who signed up 
on the day of J une 14, 2000, you could use this query: 

select * from users where lastchanged between 20000614000000 and 
20000614235959 

Remember that the default timestamp column type stores dates in the form 
YYYYMMDDHHMMSS, so to get all entries for a single day, you need to start your 
range at midnight (00:00:00) and end it at 11:59:59 pm (23:59:59). 

You can also use between on text strings. If you wished to list all the last names 
in the first half of the alphabet, this query would work. Note that the following 
query will not include names that start with "m". 

select * from users where Iname between 'a' and 'm' 

USING IN/NOT IN 

The in predicate is helpful if there are several possible values for a single column 
that can be returned. If you queried the users table to get all the states in New 
England, you could write the query like this: 

select * from users 

where state = ' RI ' or 

state = 'NH' or 

state = 'VT' or 

state = 'MA' or 
state = 'ME' 

Using in, you can specify a set of possible values and simplify this statement. 
The following query would achieve the same result. 

select * from users 

where state in ('RI', 'NH', 'VT', 'MA', 'ME') 

If you need to achieve the same effect but in reverse, you can use the not in 
predicate. To get a listing of all people in the table not living in New England, sim- 
ple throw in the word 'not': 

select * from user where 

state not in ('RI', 'NH', 'VT', 'MA', 'ME') 



Chapter 3: Getting What You Want with select 53 

USING LIKE 

Of course there will be occasions when you are searching for a string, but you're 
not exactly sure what the string looks like. In cases like these, you will need to use 
wildcard characters. In order to use wildcards, you need the i i ke predicate. 

There are two wildcard characters available, the underscore (J and the percent 
sign (%). The underscore stands for a single character. The percent sign represents 
any number of characters, including none. 

So, for example, if you were looking for someone with the first name of Daniel 
or Danny or Dan, you would use the percent sign. 

select * from users where fname like 'Dan%' 

Note that because the percent sign will match on zero characters, the preceding 
query would match the name "Dan". 

However, if for some odd reason you needed to find all of the people in your 
database with four-letter first names beginning with the letter J, you'd construct 
your query like this: (Note that three underscores follow the letter J .) 

select * from users where fname like 'J ' 

The three underscores will match any characters and return names like Jean, 
J ohn, and J ack. J ay and J ohnny will not be returned. 



In MySQL the 1 i ke comparison is not case sensitive. This is quite different 
from most implementations. 




order by 



There is one thing you should always keep in mind when working with relational 
databases: the storage of rows in any table is completely arbitrary. In general, you'll 
have no idea of the order in which your database has decided to put the rows 
you've inserted. When it matters, you can specify the order of rows returned in 
your query by tacking order by on the end of it. 

This command can sort by any column type: alphabetical, chronological, or 
numeric. In addition, order by allows you to sort in either ascending or descend- 
ing order by placing asc or desc after order by, respectively. If neither is included, 
asc is used by default. 

To alphabetize the entries in the table, you would probably want to make sure 
that this list is sorted by both first name and last name: 

select * from users order by Iname, fname 



54 Part I: Working with MySQL 

You can sort by as many columns as you wish, and you can mix the asc and 
desc as you need. The following query isn't particularly useful, but it is possible: 

select * from users order by Iname asc, fname desc 

limit 

The l imit predicate will restrict the number of rows returned from your query. It 
allows you to specify both the starting row and the number of rows you want 
returned. To get the first five rows from the table, run the following query: 

select * from users limit 0,5 

To find the first five rows alphabetically, you could use i imit with order by: 

select * from users order by Iname, fname limit 0,5 

You'll probably notice that the numbering is like arrays— the first row is row 0. 
To get the second five rows of the table, you'd run the following: 

select * from users limit 5,5 

limit is particularly useful in situations where you want to restrict the display 
on any one page. You'll seethe use of i imit throughout this book. Even Chapter 8, 
which is the first application in this book, uses i imit. 

group by and aggregate functions 

Remember back to when we were talking about using select with distinct and 
how that removed rows we didn't need? That may have seemed pretty cool, but it's 
nothing compared to what you can get out of using group by and its associated 
aggregate functions. 

Consider this task: you wish to know the number of entries from each state in 
our database (for example, six from California, seven from New York, two from 
Vermont). If you did a sel ect distinct state from users order by state, 

you would get a listing of each state in the database, but there's no way to get the 
numbers. As MySQL goes through the table to process the query, it simply skips 
over rows that would return identical values. 

However, with group by, MySQL creates a temporary table where it keeps all of 
the information on the rows and columns fitting your criteria. This allows the 
engine to perform some very key tasks on the temporary table. Probably the easiest 
way to show what group by can do is by showing one of the aggregate functions. 
We'll start with countO. 



Chapter 3: Getting What You Want with select 



55 




MySQL may not actually create a temporary table for each group by; how- 
ever, the actual inner workings of a group by are pretty complex,and this is a 
good way to think about what MySQL is doing. 



COUNT() 

Once again, the goal of your query is to find out the number of people from each 
state that are in your users table. To do that you will use group by with count( ). 
Remember that when the group by clause is used, you can imagine MySQL cre- 
ating a temporary table where it assembles like rows. The count( ) function then 
(you guessed it) counts the number of rows in each of the groups. Check out the fol- 
lowing query and the result returned in Figure 3-5. 

select state, count(*) from users group by state 



http://192.168.1.1/book/ch3 functions.php?query^select+state,+countn+from+users+gro... HE3E3 



File Edit View Favorites lools Help 



Address |@ ch3Junctions.php?querji=select+state^2C+counK28"^29+from+users+group+bji+state+ J^J i^Go J Links 



*" „ ■► „ © m a 

Back Stop Refresh Home 



m m 

Search Favorites History 



Print 



Edit 



query: select state, count(*) from users group by state 



state county*) 


CA 4 


FL 1 


|ma |i 


|nd |i 


|hy |2 


|OR |l 


|tn |i 


|VT |l 



Submit Query 



lastarti g!i<>gia^ [#>7kfm.| nawg'^p-s s^^^o mo am 



Figure 3- 5: Results of query using select state, count(*) from users group by state 



Here the asterisk (*) indicates that all rows within the group should be counted. 
The court(*) function is also handy for getting the total number of rows in a 
table. 



select countt*) from users 



56 



Part I: Working with MySQL 



Within a group by, you can also indicate a specific field that is to be counted. 
count will look for the number of non-null values. Take, for example, the table in 
Figure 3-6. 



3 http://192.168. 1.1/book/ch3 funrtions.php?query^select+*+from+user ayes - Microsoft ln...HbiEa 



Edit View Favorites Tools 




Address |g http:/V1 92.1 68.1 .1 /book/ch3Junctions.php?query=select+"+from+user_ages 



<P T =► T @ [3 



Stop Refresh Home 



m. a I b- 

Search Favorites History Mail 



query: 


select * fromuserages 


name 


city jstate 


1 a s e 


|john 


|S an Francisco |CA 


|23 


|AUen 


|NewYork |NY 


|3S 


|Bill 


[Chicago |lL 


|41 


Jean 


SanFrancisco CA 


NULL 


Barney 


phicago |lL 


|32 


Ray 


|NewYork |NY 


NULL 



Submit Query 



l*Start| 0^,9 



[iii m\ sf'I gjtl &\ 



o^'i-O-t ^^*-oO 11:25 AM 



Figure 3-6: users ages table 

It may not seem that there's much use in counting non-null values from this 
table. However, if you're the type that's really into statistics, you could use this 
table to figure out what percentage from each city feels comfortable indicating its 
age. First you'd need a count of all the entries from each specific city and state; fol- 
lowing that, you'd need a count of all the non-null values in the age field. 

Select city, state, count(*), count(age) from user_ages 
group by state, city 



From the result in Figure 3-7, you can see that Chicagoans are far more forth- 
coming than those from the coasts. 

This is as good a time as any to introduce aliases. There will be times, particu- 
larly when you're working with functions, when the column name returned by the 
query isn't what you'd like it to be. For example, in Figure 3-7 you may wish for a 
table header a bit more descriptive than countc*). 

You can follow any function or column name with the word as and then specify 
a name you prefer, as simply designates an alias. If you need a column name that 
is more than one word, surround the text string with single quotes. 



Chapter 3: Getting What You Want with select 



57 



3 http://192.168. 1.1/book/ch3 functions.php?query=Select+city,+state,+countf),+coijnt(a(i... HElES 



File Edit View Favorites lools Help 



Address |;S] te^2C+counK28"X29X2C+courtS28age^29+from+user_ages+group+bji+state^2C+citji J^J i^Go 



Links : 



Back Stop Refresh Home Search Favorites History 



Print 



Edit 



query: Select city, state, count(*), count(age) from userages group by state, city 



~J 



city |state |coiuit( + ) 


count(age) 


|SanFrancisco |CA |2 |l 


|Chicsgo |IL |2 




2 


|NewYork |NY |2 |l 



Submit Query 



aastarti gftioffl "Uleifflcl^ljgjtl^ji |nau>»'ff i >o^ j-/^^a 1 1:33 am 



Figure 3- 7: Results of query using count( ) function 

While on the topic of aliases, I'll also mention that there are a variety of func- 
tions available in MySQL (see Appendix I). They range from simple math functions 
to more complex operations. Below I've thrown in some math to clarify the purpose 
of the query. Notice the use of the as clause and the way it affects the display of the 
query (shown in Figure 3-8). 

Select city, state, count(*) as 'Total Rows', 

count(age) as 'The Willing', 

(count(age)/count(*)*100) as 'Percent Responding' 
Trom user_ages 
group by state, city 



You can also use aliases on tables. This will be particularly helpful when deal- 
ing with multiple tables. I'll discuss this in further detail in the section "Multi-table 
join." 

SUM() 

The sum( ) function returns the sum of a given column and is almost always used 
with a group by clause. For instance, if you are running an application for a non- 
profit, you might want to know the total contributions from each state. The table 
you're working with might look like the one in Figure 3-9. 



58 



Part I: Working with MySQL 



'3http://192.168.1.1/book/ch3 funrtions.php?query^Selert+city,+state,+countn + as+Total+...H0H 



File Edit View Favorites lools Help 



Address |*S] 29"1 00S29+as+^27Percent+Responding^27+from+user_ages+group+bji+state^2C+citji J^J i^Go 



Links : 



Back Stop 



Refresh Home 



© a 

Search Favorites History 



Mail 



Print 



Edit 



query: Select city, state, count(*) as 'Total Rows', count(age) as 'The Willing', (count 
(age)/count(*)*100) as 'Percent Responding' from userages group by state, city 



city |state {Total Rows 


The 


Willing 


[Percent Responding 


|S an Francisco |CA \2 


1 




|J0.00 


|Chicago |IL |2 


V 




1 100 .00 


|NewYork |NY |2 


1 




|j0.00 




I II 



Submit Query 



lastarti ^ftoffl »uie ffl|g|jg]|@|jjj ||Jau»'ff'^o^ j./o^e 1 1:49 am 



Figure 3-8: Results of query using functions and aliases 



'3 http://192.168.1.1/book/ch3 fu n cti o ns. p h p? q u ery=se I ect+ :t +from+ contributions - Microsof... RbiEa 



File Edit View Favorites lools Help 



Add re ss|ghttp://1 92. 1G8.1.1/book/ch3_f unctions. php?querv=select+"+from+contributions ^\ ^ Go J Links 



<P T =► T @ [3 



Stop Refresh Home 



m. a 

Search Favorites History 



query: select * from contributions 



userid state contribution 


|l |CA [50 


|2 |NY |100 


|3 |CA [173 


|4 |TX |600 


\5 |NY |1200 



Submit Query 



iiastart| @<ji*hm" W\MMMM 



•■ff'^O-J £)£*>ln® 11:59 AM 






Figure 3-9: Table where using sum() would be helpful 



Chapter 3: Getting What You Want with select 59 

To get the total from each state, you'd run the following query: 

select state, sum(contri buti on) from contributions group by state 

MIN() 

The min( ) function pulls out the lowest value in each grouping. To find the lowest 
contribution from any state just make a small change to the previous query: 

select state, mi n(contri buti on) from contributions group by state 

MAX() 

As you probably guessed, max( ) will return the highest value in a group: 

select state, max(contri buti on) from contributions group by state 

AVG() 

avg ( ) returns the average of the group: 

select state, sumtcontri buti on) from contributions group by state 

You could throw all these together to create a pretty useful query, as Figure 3-10 
and the following query show. 

select state, sum(contri buti on) as 'Total', 

avg(contri buti on ) as 'Average', 

mi n(contri buti on ) as 'Minimum', 

max(contri buti on ) as 'Maximum' 
from contributions 
group by state 

GROUP BY OPTIONS 

Most relational databases require that fields listed in the select clause be used in 
the group by predicate. But MySQL gives you more options; you can group a sub- 
set of the columns listed. For instance, if you wanted to find out the number of 
people in one city and get a look at a sample Zip code from that city, you could run 
the following: 

select city, zip, count(*) from users group by city 

The query would return a listing of cities, the number of entries for each city, 
and one sample Zip code. 



60 



Part I: Working with MySQL 



^ihttp://192.168.1.1/book/ch3 functions.php?query=select+state,+sum(contribution)+as+T... HbiEa 



File Edit View Favorites lools Help 



Address ||S] 7^2C+maK%2Scc:ri«ibLifon5;29^c!S4-^27M3K!rnum^27+from+contribuNons+group+bv+state_^J £/Go 



Links : 



Back Forward Stop 



Refresh Home 



m, m 

Search Favorites History 



Mail 



Print 



Edit 



query: select state, sum(contribution) as 'Total', avg(contribution) as "Average 1 , min 
(contribution) as 'Minimum', max(contribution) as 'Maximum' from contributions group by 
state 



state Total Average Minimum Maximum 


|CA \225 |112J000||J0 


|173 


|NY |l300 |S50.0000 |ioo 


|l200 


|tx |soo leoo.oooo |eoo 


|600 



Submit Query 



;ast a n| mn^^m jllmMMMM 



o^^O-i 3S*>*®0 11:11AM 



Figure 3- 10: Using multiple aggregate functions together 



This is quite different from the results from this query: 

select city, zip, count(*) from users group by city, zip 

This returns a separate row for each city/zip combination and provides a count 
for each unique combination. 



having 



The having predicate restricts the rows displayed by a group by. This is not the 
same as the where clause. The where clause actually restricts the rows that are used 
in the group by. The having clause only prevents their display. 

If you needed to find the average amount of donations from each state for all 
those who contributed more than $100, you could run the following: 

select avg(donati ons ) , state where donations) 100 

However, if you wanted to display average contributions for all of the states 
where the average was over $100, you would have to use the having clause. Since 
the having clause does not restrict rows that go into the group by, the aggregate 
functions, in this case avg( ), use all the rows in their calculations. 



Chapter 3: Getting What You Want with select 



61 



select avg(contribution) as avg_contrib, state 

from contributions 

group by state 

having avg(contribution)>500 



Joining Tables 



If you read Chapter 1, you know that relational databases work so well because 
they segment information. Different tables hold information on different topics, 
and fields are inserted into the tables to maintain relationships. After you finish the 
normalization process, it's likely that none of your tables will be usable without the 
others. That is why you'll need to join tables in your SQL sel ect statements. 



Two- table join (the equi- join) 



For the sake of continuity, we're going to reprise a couple of tables first seen in 
Chapter 1. Take a look at the familiar tables in Figure 3-11. 



companies 



companyjd 


company_name 


address 


1 


Big Co Company 


1121 43rd St 


2 


Little Co Company 


4444 44th St 



contracts 



contract id 


companyjd 


Name 


Title 


Phone 


Email 


1 


1 


Jay Greenspan 


Vice President 


4155551212 


1121 43rd St 


2 


1 


Brad Bulber 


President 


4155552222 


4444 44th St 


3 


2 


John Doe 


Lacky 


2125556666 


4444 44th St 



Figure 3- 11: Tables in need of a join 



If you're looking to do a mailing to all of the people in the contacts table, you 
are going to need to join the contacts table to the companies table, because the 
street address is in the companies table (and it's exactly where it should be). The 
companyjd column in the contacts table creates the relationship between these 
tables. And if you join these tables on occasions where the companyjd field in the 
contacts table is equal to the companyjd field in the contacts table, all of the 
information will be at your fingertips. 



62 



Part I: Working with MySQL 



This is easy enough to accomplish in SQL. In the from portion of the select 
statement all of the tables to be joined must be listed. And in the where portion, the 
fields on which the join takes place must be shown: 



select * 

from companies, contacts 

where companies . company_ID 



contacts. company_ID 



At times when a reference to a field name is ambiguous, you need to specify which 
table the column comes from the by using the syntax tabie_name.coiumn_name. This is 
done in the where clause in Figure 3-12. If you fail to indicate the table from which 
you're pulling thecompanyjd column in the SQL statements, MySQL will return an 
error. 



bl!tfBWL«^tKB,l,l,l!JiJ.klJI.IJI,I.L-l,I.BHm!BB!Bffi 



File Edit View Favorites lools Help 



Address ||S] m+companies^2C+cGnSacts+where+companies.companv_ID+^3D+contacts.cornpanv_ID_^J if^Go J Links 




<p T T © S 



Stop Refresh Home 



m. a 

Search Favorites History 



query: select fhame, lname, companies.company_ID, companyname, company_address 
from companies, contacts where companies.companylD = contacts. companylD 



|fiiajne 


Lnajne 


company 


JD 


company name 


company address 


Bay 


greenspan 1 




|big company 


|5:5:5 5th st 


|brad 


|bulger 


\2 




(little company 


|o"6"6" 6th st 




II 



Submit Query 



:ast a n| % <p & » j|g .ai^jg^] ||Jau»'ff'^o-j ^^^>i<»6> 1:57 pm 



Figure 3-12: A basic join 



This type of join, where tables are merged based on quality in a common field, is 
extremely common. It is known as an equi-join, or an inner join. The name "inner 
join" will make more sense once you learn about the outer join later in this chapter. 

Once you begin performing joins, aliases become convenient. By specifying an 
alias in the from clause you could save yourself some typing. In the following code, 
ti is an alias for companies and t2 is an alias for contacts. 



Chapter 3: Getting What You Want with select 



63 



select * 

from companies tl, contacts t2 

where tl . company_ID = t2 . company_ID 

Multi- table join 

An equi-join can be applied to more than one table. Many of your SQL statements 
will join three, four, or more tables. All you'll need to do is add additional columns 
after sei ect, additional tables in the from clause, and the additional join parame- 
ters in the where clause. Take a look at the tables that need multiple joins in 
Figure 3-13. 



companyjd 


name 


1 


IBM 


2 


Xerox 


3 


Sun 




companyjd 


expertisejd 


1 


1 


1 


2 


1 


3 


2 


1 


2 


3 


3 


1 


3 


2 



expertisejd 


area 


1 


Hardware 


2 


Software 


3 


Consulting 





1 






location id 


companyjd 


address 


state 


1 


1 


4 My Way, Durham 


NC 


2 


2 


44 Circle Dr, New York 


NY 


3 


1 


1 Front St, San Francisco 


CA 


4 


2 


Park Dr, Palo Alto 


CA 


5 


2 


48 Times Square, New York 


NY 


6 


3 


280 South, Sunnyvale 


CA 



Figure 3- 13: Tables in need of multiple joins 



If you wanted to find the addresses for all of the companies with offices in 
California who had expertise in consulting, you would have to join all four of these 
tables. The following query would get the job done. Here the where clause contains 
quite a few tests; the first two lines of the where clause limit the rows that will be 
returned to those companies that match our criteria. The remainder of the where 
clause takes care of the joins. 



64 



Part I: Working with MySQL 



SQL-query: 

Select * 

from companies, locations, expertise, companies_experti se 

where state = 'CA' and 

compani es_experti se .experti se_ID = 3 and 

compani es . company_ID = compani es_experti se . company_ID and 

compani es . company_ID = 1 ocati ons . company_ID and 

compani es_experti se .experti se_ID = experti se .experti se_ID 



outer join 



The challenges presented by null values have shown themselves repeatedly in this 
book. In Chapter 2 we presented these two tables, seen in Figure 3-14. 



first name 


last name 


spousejd 


jay 


Greenspan 


1 


Brad 


Bulger 





spousejd 


spouse_first_name 


spouse_last_name 


1 


Melissa 


Ramirez 



Figure 3- 14: Tables in need of an outer join 

Now imagine that you need to get a list of the authors of this book and their 
spouses, if they are married. The equi-join shown in the previous section will not 
work in this case. Take the following query: 

select * 

from contact, spouses 

where contact . spouse_ID = spouses . spouse_ID 

Only the first row of the contact table will be returned. The null value in the sec- 
ond row ensures that nothing can match the criteria in the where clause. In cases 
like this, where we need to preserve one table and join the second table when there 
are matching values, we will make use of the outer join (also known as the left 
outer join), which looks like this: 

select * 

from contact 

left join spouses 

on contact . spouse_ID = spouses . spouse_ID 



This statement says, "I want to keep the entire contacts table, and tack on the 
spouses table when these two fields are equal." The word left in the term left outer 



Chapter 3: Getting What You Want with select 65 

join refers to the fact that when you visualize your database tables, you should 
visualize the first table, the one that appears in the from clause, on the right-most 
side, and the joined table on the left. 

Depending on the database package you're using, the syntax of the outer join 
may vary. Some databases support left, right, and full (both left and right) outer 
joins. MySQL only has the left outer join, but in practice it's usually all you need. 
You can use the syntax seen in the previous query, or you can use left outer 

join on. 

Outer joins will come up frequently out of necessity. Additionally, it is often 
good practice to use outer joins even when you feel an inner join will do the trick. 
It's just a matter of being safe. You'd rather not have important rows of data come 
up missing because you forgot to account for null values. Throughout the book, 
you will see occasions when we have used outer joins because wejust wanted to be 
extra careful. 

There may come times when you will need to do more than one outer join. Say, 
for instance (and for no particularly good reason), we wanted to store information 
regarding spouses' siblings. We'd add another table listing the siblings and a col- 
umn to the spouses table, which maintained the relationship. So, if we were to 
design a query that maintained everyone in the contacts table, and maintained 
everyone returned from the spouses table, we'd have to throw in two outer joins: 

select * 

from contact 

left join spouses on contact . spouse_ID = spouses . spouse_ID 

left join on syblings spouses . sybl i ng_ID = sybl i ngs . sybl i ng_ID 



self join 



As bizarre as it may sound, the time will come when you'll need to join a table to a 
copy of itself. You'll usually run into the need for this when looking for duplicates 
in a table. If we had a sneaking suspicion that there was a bigamist in Table 3-2, 
how would we search out the two with the same spouse? 



Table 3-2 CONTACTS 


contactid firstname 

1 jay 

2 brad 


lastname 

greenspan 
bulger 


spouseid 

1 


Continued 



66 



Part I: Working with MySQL 



Table 3-2 CONTACTS (Continued) 



contact_ id 


firstname 


lastname 


spouse_ id 


3 


John 


James 


2 


4 


elliot 


simms 


2 



You would need to discover if the value in this spousejd field was repeated (for 
instance, the number 2 appears more than once). You could do a group by, but 
then there would be no way of getting the names of the people involved. Using 
group by along with the count( ) function, you could find the occasions where one 
person appears more than once, but it would take a second query to find out who 
those people were. With a self join, you can do it all in one step. But it needs to 
be a carefully considered step. 

You might think that the following query would do the trick. Notice that I again 
use an alias, so that we have two table names we can address. 



select tl . f i rst_name , tl . 1 astjame , 
from contacts tl, contacts t2 
where tl . spouse_id = t2 . spouse_i d 



t2.fi rst_name , t2.1ast_name 



But this is going to return more rows than we need. Specifically, each name will 
match itself, providing duplicates of each returned entry. Given this query, when 
the row for Jay is compared to itself, it will test true and be returned in the result. 
You can eliminate redundancy here by ensuring that the contactjd field from the 
first table is not equal to the ID field in the second table. 

select tl . f i rst_name , tl .1 ast_name 
from contacts tl, contacts t2 
where tl.spouse_id = t2.spouse_id 
and tl . contact_i d != t2 . contact_id 

This is good but not perfect. Take the example of Elliot and J ohn. A row will be 
returned when the Elliot is in tl and J ohn is in t2; another will be returned when 
John is in tl and Elliot is in t2. The easiest way to address that here is to make use 
of the numeric primary key. You know one ID will be greater than the other, and by 
using that information you can get rid of all duplicates. 



select tl . f i rst_name , tl.last_name 
from contacts tl, contacts t2 
where tl.spouse_id = t2.spouse_id 
and tl . countact_id < t2 . contact_id 



Chapter 3: Getting What You Want with select 67 

Portions of SQL the SQL Standard 
that MySQL Doesn't Support 

The MySQL developers are constantly working on improvements to the software. It 
is possible that within the next couple of years they will support most of the fea- 
tures you'd find in high-priced commercial software, like Oracle, Sybase, Informix, 
or Microsoft's SQL Server. But as of the writing of this book, there are a couple of 
portions of the sel ect syntax that MySQL doesn't support. 

Unions 

Unions allow queries with the same number of columns to be returned in one result 
set. For instance, if you had two tables storing user names, you could have all of 
the names in one query returned with a statement like this: 

select first_name, last_name 

from table_l 

union 

select first_name, last_name 

from table_2 

Unions are convenient, but their absence in MySQL isn't that big of a deal. In the 
preceding example, you could easily run a second query. 

Correlated subqueries 

If you're coming from a background of using a package like Oracle, you may find 
the absence of correlated subqueries troubling. The good news is that subquery 
support is high on the developers' priority list. For those new to the concept, sub- 
queries allow you to define an entire query in the where clause. 

For example, using subqueries, if you had a table that stored students and their 
test scores, you could easily find all the students with better-than-average test 
scores: 

select first_name, last_name, score 

from test_scores 

where score) (select avg(score) from test_scores) 

You can achieve the same effect by running two queries. In all cases you can 
work around the absence of subqueries by running additional queries. You lose 
some elegance, but the effect is identical. 



68 



Part I: Working with MySQL 




Make sure to check in at the mysql.com every now and then. Subqueries 
may be included in version 3.24. And by the time you're reading this, that 
version may be available. 




In Chapter 10 there is more information on dealing with subqueries in 
MySQL 



Summary 



You can get through the better part of your life without committing some portions 
of SQL to memory. If you are using graphical tools you may not need to learn the 
specifics of the create or al ter command. The same cannot be said of the sel ect 
statement. 

Everything covered in this chapter is really important to your life as an applica- 
tions developer. The sel ect statement allows you efficiently retrieve and sort infor- 
mation from your databases, and if you understand the intricacies of the select 
statement, you'll be able to write applications more efficiently and elegantly. 



Chapter 4 

Getting Started with 
PHP- 



IN THIS CHAPTER 

♦ Assigning variables within PHP scripts 

♦ Handling data passed from HTML forms 

♦ Working with PHP's built-in variables, including Apache variables 

♦ Testing for and assigning variable types 



PHP makes working with variables extremely easy. PHP is smart about under- 
standing variable types and keeps the syntax to an absolute minimum. Those com- 
ing to PHP from a CJava, or Perl background may find PHP easier to deal with, but 
the ease of syntax can present its own problems. 

All variables in PHP start with a dollar sign ($). It doesn't matter what kind of 
variables they are, whether strings, integers, floating-point numbers, or even 
arrays. They all look identical in the code. The PHP engine keeps track of the type 
of information you are storing. 

In general, variables will come from three places: they are either assigned within a 
script, passed from an HTML page (often from form input), or part of your PHP envi- 
ronment. We'll talk about each of these in the following sections. Note that variables 
can come from other places: URLs and sessions are also possible origins of variables. 

Assigning Simple Variables 
Within a Script 

PHP does not require explicit variable declaration. All you have to do is use a vari- 
able and it exists. And as we already mentioned, all variable types look identical. 
The following code shows how to assign variables of string, integer, and floating- 
point (double) types: 

$ a = "this is a string"; //this is a string 

$b = 4; //this is an integer _. 



72 Part II: Working with PHP 

$c = 4.837; //this is a floating-point number 
$d = " 2 " ; //this is another string 

Notice that the = is the assignment operator. For comparison, you must use two 
consecutive equal signs (= =). For example, if($x==i). 

Typing is flexible, and PHP is pretty smart about handling changes in types. For 
example, given the code you just saw, the following would evaluate as you'd prob- 
ably hope: 

$e = $b + $d; 
echo $e; 

PHP would recognize that you want to treat the string in $d as an integer. The 
variable $e will be an integer type and will equal 6. In fact, PHP will also evaluate 
the following as an integer: 

$a = 2; 

$ b = "2 little piggies"; 

$c = $a + $b; 

Here, $c will equal 4. If an integer or floating-point number is at the beginning 
of a string, PHP can evaluate it as such. Similarly, PHP will move smoothly among 
numeric types. 

$ f = 2; / / $ f is an integer 

$g = 1.444; // $g is a double (floating-point) type 

$f = $f + $g; //$f is now a double type 

This kind of flexibility is nice, but it can lead to some difficulty. There will be 
times when you're not sure what variable types you are working with. We'll show 
you how to deal with these circumstances in the section "Testing Variables." 



Delimiting Strings 



In the preceding code, all the strings were surrounded by double quotes. There are 
two other ways to delimt strings in PHP. 

If you surround your strings with double quotes, variables within the string will 
be expanded. For instance, 

$my_name = "Jay"; 

Iphrase = "Hello, my name is, $my_name"; 

echo $phrase; 



Chapter 4: Getting Started with PHP -Variables 73 

will print "Hello, my name is, Jay". But if you want to include any of the following 
characters within you string, they must be escaped with backslashes: 

♦ " (double quotes) 

♦ \ (backslash) 

♦ $ (dollar sign) 

For example, to print an opening form tag using double quotes you would have 
to do the following. 

echo "<form acti on=\"mypage . php\" method = \"get\">" ; 

You can also surround strings with single quotes. If a string is within single 
quotes, variables will not be expanded. So this code: 

$my_name = " Jay " ; 

echo 'Hello, my name is, $my_name'; 

will print "Hello, my name is, $my_name". The only characters that need to be 
escaped within single quotes are single quotes and backslashes. 

Finally, starting in PHP 4, you can make use of Here documents. This is a hybrid 
of the single and double-quote style that can be convenient in many circumstances. 
Here docs are delimited at the start of the string with three less- than signs <« and 
an identifier. In the book we use the identifier EOQ. The string is terminated with 
the same identifier followed by a semicolon. In the following, $my_string is a 
string properly delimited using Here doc syntax. 

$my_string = <<<E0Q 
My string is in here. 
EOQ; 

Using Here docs, variables will be expanded within string and double quotes do 
not need to be escaped. We make frequent use of Here docs when working with 
form elements. 

$element = <<<E0Q 

<textarea name="$name" cols="$cols" rows="$rows" 

wrap="$wrap">$value</textarea> 

EOQ; 

In a case like this we don't need to litter the string with backslashes, and we still 
get the convenience of having variables expanded within the string. 



74 



Part II: Working with PHP 




Array elements accessed by associative keys cannot be expanded in Here 
docs. For example, the following will produce an error. 

$array = array ( "fname"=>" jay" , "1 name"=>"greenspan" ) ; 

$str = <<<E0Q 

print my string larray [ "f name" ] 

EOQ; 



Assigning arrays within a script 

Arrays are variables that contain multiple values. A simple array might store the 
months of the year. To assign this array, you could use the following: 

$months = array ( "January " , "February", "March", "May", "June", 
"July", "August", "September", "October", "November", "December"); 

This array has 12 elements, and you can address them by their ordinal placement 
in the array, starting with 0. So the command echo $months[0] would print 
J anuary and echo $months[ii] would print December. To print out all of the val- 
ues within an array, you could get the length of the array and then set up a loop. 

for ($month_number=0 ; $i <count( $months ) ; $i++) 
{ 

echo $months[$month_number] . "<br>\n" ; 




The for loop is explained in Chapter 5. 



You can also assign values to arrays with a simple assignment operator. The fol- 
lowing would work: 

$dogs = array ( ) ; 
$dogs[0] = "shepherd"; 
$dogs[l] = "pood! e" ; 

If you don't specify the numeral, the value will be tacked on the end of the array. 
The following line would assign "retriever" to $dogs[2]. 



fdogs[] = " retri ever" ; 



Chapter 4: Getting Started with PHP -Variables 



75 




There are a variety of functions that work with arrays (over 40 in PHP 4). 
Many of these will be covered in Chapter 6. 



Like many programming languages, PHP makes use of associative arrays. If you 
are new to the concept, elements in associative arrays have "keys" that reference 
individual elements. This is particularly important when you're dealing with data- 
bases. When you fetch rows from your database query you will usually refer to the 
elements by their keys. 

You can assign an associative array in this manner. Here, first_name, 
iast_name, and e-mail are the keys. 

$person = array ( 

"f i rst_rame" => "Jay" , 

"last_rame" => "Greenspan", 

"e-mail" => "jgreen_l@yahoo.com" 
); 

If you wanted to add to this array, you could assign another value. Notice that 
the next line would add an integer into the array, so this array would contain four 
values, three strings and one integer. 

$person["age" ] = 32; 

Typically, if you want to access both the keys and the values in an associative 
array, you would use i ist( )=each( ), as in the following code. 

while (list($key, lvalue) = each( Sperson) ) 
{ 

echo "<b>key :</b> $key, value = $value <br>\n"; 



Chapter 5 describes the list()=each() in more detail. Basically, eachQ pulls the 
key and value of a single array element; listo takes those values and assigns 
them to $key and $value, respectively. This process continues until each element in 
the array has been accessed. If you want to go through the array a second time, you 
will need to reset the array pointer with reset(iperson). 

If you wanted to get only the value without the key, or if you were using a non- 
associative array and wanted to use the l ist( )=each( ) structure, you would have 
to do this: 



while (list( , lvalue) = each( Sperson ) 



76 



Part II: Working with PHP 



echo "value = $value <br>\n"; 
} 

Or, if you want to get at just the keys, you could do this. 

while (list(lkey) = each( $person ) ) 
{ 

echo "key = $key <br>\n"; 
} 




Think about PHP arrays this way: all arrays are associative. A couple of pages 
back you saw you can assign a basic array without specifying associative 
keys. For example $myarray= array ("pug", "poodl e" ).When thisis 
done, PHP assigns $myarray consecutive numeric keys starting at zero. 
They behave just like associative keys. You step through them using listO 
=each( ).They make use of the same array functions, many of which are 
explained in Chapter 6. 



Assigning two-dimensional arrays in a script 

PHP also supports multi-dimensional arrays. The most commonly used multi- 
dimensional array is the two-dimensional array. Two-dimensional arrays look a lot 
like tables. They store information that is based on two keys. For instance, if we 
wanted to store information on more than one person, a two-dimensional array 
would work well. We would assign an array named $peopl e, and within $peopl e 
there would be individual arrays addressing each person: 

Ipeople = array ( 

"jay" => array ( 

"last_name" => "greenspan", 
"age" => 32 
), 
"John" => array ( 

"1 ast_name" => "doe" , 
"age" => 52 
) 
); 

Here the $peopl e array contains information on two people, J ay and J ohn. To 
access information on any single value, you will need to use both keys. To printout 
J ohn's last age, the following two commands would work: 

echo $people[ " John "][" age"]; //prints 52 



Chapter 4: Getting Started with PHP -Variables 77 

You could access all of the elements in a two-dimensional array by looping 
through both of the array's dimensions: 

whi 1 e( 1 i st( $person , $person_array ) = each( $peopl e) ) 
{ 

echo "<b>What I know about $person</b><br>\n" ; 

whi 1 e( 1 i st( $person_attri bute , lvalue) = each( $person_array ) ) 

t 

echo "$person_attri bute = $val ue<br>\n" ; 



Accessing Variables Passed from the 
Browser 

The whole point of using PHP, or any other middleware package for that matter, is 
to deliver customized information based on user preferences and need. Often, the 
information will come via HTML forms. But information can come from other 
places, including HTML anchors, cookies, and sessions. 

HTML forms variables 

One of the most common ways in which variable information is delivered is 
through HTML forms. 



Appendix A presents detailed information on creating HTML forms. Refer 
to that appendix before you read this section if you are unfamiliar with 
xref | this topic. 



For each of your form elements you will have to assign a name and a value 
attribute. When the form is submitted, the name=value pairs are passed to PHP. 
They can be passed to PHP by either the GET or POST methods, depending on what 
you chose in your form action attribute. 

Once a form is submitted, the form elements automatically become global vari- 
ables in PHP. (Global variables and variable scope are discussed in Chapter 6). It is 
truly a no-muss, no-fuss way of doing business. Consider the following simple 
HTML form: 

<form acti on=mypage . php action=post> 
<input type=text name=email> 




78 



Part II: Working with PHP 



<input type=text name=fi rst_name> 
<input type=submi t name=submit value=add> 
</form> 

Once the user hits the submit button, variables named $email, $first_name, 
and $submit will be available in the called PHP page. You can then process these 
variables however you see fit. Note that in most of our applications we will be 
using the value of the submit button, to make sure the page understands what 
action the user has taken. The following is a brief example of how this will work. 
Assume the name of the page is mypage.php. 



<?php 

if ( i sset( $submi t) 



$submi t=="yes' 



echo "thank you for submitting your form. 

) else ( 

?> 

<form acti on=mypage .php action=post> 
<input type=text name=email> 
<input type=text name=f i rst_name> 
<input type=submit name=submit value=yes> 

</form> 

<?php 



?> 



In some browsers if there is only one submit button within a form, the user 
can hit the enter key and submit the form without the submit button infor- 
mation being sent. 




On his or her first visit to this page the user will be presented with a form. Once 
the form is submitted and the page recalls itself with the new variable information, 
only the thank you message will appear. 

Form variables will also be accessible through either the $http_post_vars or 
$http_get_vars array, depending on the method used in your form. These are 
convenient if you have variables coming from both methods, if variables from 
forms could carry the same name as variables in your script, or if you have an 
undefined set of variables being passed and you need to check what's there. 



Chapter 4: Getting Started with PHP -Variables 



79 



If you are wondering when you might have to deal with variables from both GET 
and POST, consider a situation where a user gets to a page by clicking on a link 
with querystring information. The user may then end up at a page with a form. If 
the action of form is an empty string, the form will submit to itself and it will main- 
tain the querystring If the method of the form is POST, variables will be coming 
from both GET and POST 

You can access any individual element like any associative array ($HTTP_ 
POST_VARS["e-mail"]). Or you can loop through all of the contents of the array as 
follows: 

while (listdkey, lvalue) = each($HTTP_POST_VARS) ) 
t 

echo "variable = $key value = lvalue <br>"; 



Passing arrays 



There are occasions when passing scalar variables won't work, and you'll need to 
pass arrays from your HTM L page to your PHP script. This will come up when your 
user can choose one or more form elements on a page. Take, for example, multiple 
select boxes, which allow users to pass one or more items from a number of items. 
The form element is made with the HTML in the following code example. The "mul- 
tiple" attribute indicates that the user can choose more than one element, as shown 
in Figure 4-1. To choose more than one element on the PC, hold down the Ctrl key 
while selecting additional values. On the Mac, use the Apple key, and you Gnome 
users can select and unselect individual elements with a click. 



<form action ="mypage . php" method="post"> 
<select name=" j_names[] " size=4 multiple) 

<option val ue="2">John 

<option val ue="3">Jay 

<option val ue="4">Jacki e 

<option val ue="5">Jordan 

<option val ue="6">Jul i a 
</sel ect> 

<input type="submi t" val ue="submi t"> 
</f orm> 



80 



Part II: Working with PHP 



-' D:\Work-edrive\book\ch4\forfig1.html - Microsoft Internet Explorer 



File Edit View Favorites lools Help 



Address |jS] D AWork-edrive\book\ch-4\forfig1 .html 



"31 t*Go 



Links : 



4" . -► . © E tS 

Back Stop Refresh Home 



© a 

Search Favorites History 



Mail 



Print 



Edit 




lastarti m^^mm^® |]jj^|_a| Tj|[ii3Vff)»£>^ msw 3:13am 



Figure 4-1: Multiple select box 

Notice that in the select "name" attribute we've added opening and closing 
brackets ([]). This tells PHP to expect an array. If we didn't include the bracket, 
there could be two values fighting for the same variable name, and that's no good 
at all. 

Once it has been submitted you can address this array like any other. 

if ( i s_array ( $j_names ) ) 
{ 

echo "<b>the select values are:<br> <br>"; 

whi 1 e(l i st( $key , lvalue) = each( $ j_names ) ) 

{ 

echo lvalue . "<br>\n"; 



Passing arrays can also be useful when you want to present a series of check- 
boxes that the user may or may not check before pressing the submit button. In 
Chapter 8, there is a code example for a page that allows the program's administra- 
tor to use checkboxes to select which entries should be deleted. Figure 4-2 shows a 
sample of this type of page. If we were to assign a different name to each checkbox, 
we would have to check each one individually. With arrays, we write a three-line 
loop to check them all. 



Chapter 4: Getting Started with PHP -Variables 



81 



"--■' Edit The Guest Book - Microsoft Internet Explorer 



Mi 



File Edit View Favorites lools Help 



Address |g] http://1 92.1 68.1 .1 /book/guestbook2k/edit.php?offset=0 



~Z\ &G° 



Links ; 



^ T =»> T @ r_ tfl i a a 

Back Stop Refresh Home Search Favorites History 



Print 



Edit 



Edit The Guest Book 



Name: |Jay Greenspan 



Entry date: 



Email: pgreen_l@.y ahoo.com 
Delete? |l~ Yes, delete entry #1 



Name: |Jay Greenspan 



|Entry date: 



Email: jgreen_l@yahoo.com 
Delete? |r Yes, delete entry #2 



Delete Entries 



Reset 



_ 



IJiStartl ®«ji*>&m^<4 _j|[8] M|±| i!3^.'ff>«'0'^ J./t-O^B 3:41AM 



Figure 4- 2: Series of checkboxes 

Arrays passed from forms can also have associative keys, which can be multi- 
dimensional. The name of the form element should take the form name="array_ 
name[element_name]". Or for a multi-dimensional array, array_name[element_name] 
[subelement_name]". 

Cookies 

Cookies are small pieces of information that are stored on a user's hard drive. A 
cookie contains a bit of text that can be read by the Web server that put it there. 
Cookies provide the only way to keep track of users over the course of several vis- 
its. Remember that the Web is a stateless environment. Your Web server really has 
no idea who is requesting a page. Cookies help you keep track of users as they 
move around your site. 

When they exist, cookies become part of the HTTP request sent to the Web 
server. But first you'll need to set a cookie. The developers have made this, like 
everything else in PHP, exceedingly simple. Use the setcookieO function. This 
function takes the following arguments: 



setcooki e(name , value, time_to_expi re , path, domain, security 
setti ng) ; 



82 



Part II: Working with PHP 



We will discuss this in more detail in Chapter 6, but for now, suffice it to say that 
the following statement 

s e t c o o k i e ( " my c o o k i e " , 

"my_id" , time ()+( 60*60*24*30) ,"/"," .mydomain.com", 0) 

would set a cookie with the following parameters: 

♦ Stores a variable named my_cookie 

♦ Value of mycookie "myjd" 

♦ The cookie will expire 30 days from the time it is set (current time +the 
number of seconds in 30 days). 

♦ The cookie will be available to every page in the domain. We could 
restrict it to a specific path within a domain by including a path. 

♦ It will be available to every site with a mydomain.com address. 

♦ There are no special security settings. 

Once the cookie is set, the variables retrieved from the cookie behave precisely 
like the variables retrieved from form elements. They will automatically be avail- 
able as global variables. After a PHP script places the cookie, additional scripts 
within the domain can access it directly. 

If you wanted to be careful that $mycookie didn't conflict with another variable 
also named $mycookie, you could access it through theHTTP_COOKIE_VARS array, 
using HTTP_COOKIE_VARS["mycookie"]. 

You can also set cookies that are accessible as arrays: 

s e t c o o k i e ( " my c o o k i e [ f i r s t ] " , 

"dddd",time()+2592000,'7","192.168.1.1", 0); 

setcookie("mycookie[second]", 

"my_second_id ", time O+2592000,"/", "192. 168. 1.1", 0); 

These two variables would be accessible as an associative array. 




The preceding code worked fine using Internet Explorer 5 on the PC. 
However,this may not work on other browsers. In any case, you are probably 
better off avoiding situations that require arrays within cookies. 



Chapter 4: Getting Started with PHP -Variables 83 



Sessions 



PHP 4, like ASP and ColdFusion, natively supports sessions, only it does a much 
better job. What's a session? Basically, it's another way of maintaining state 
between pages. Your script declares that a session should start with the start_ 
session( ) function. At that point PHP registers a unique session ID, and usually 
that ID is sent to the user via a cookie. PHP then creates a corresponding file on the 
server that can then keep track of any number of variables. The file has the same 
name as the session ID. 

Once the session is created, you can register any number of variables. The values 
of these variables are kept in the file on the server. As long as the session cookie 
lives, these variables will be available to any page within the same domain that 
wishes to access them. This is a much more convenient setup than sending vari- 
ables from page to page through hidden form elements or bloated cookies. 

Of course, there is the possibility that some users will not allow cookies. For this 
reason, PHP allows you to track the Session ID through thequerystring. You can do 
this manually by appending the Session ID onto the querystring, or by changing a 
configuration option. 

To add the session ID to thequerystring manually, use <?=SID?>. This automat- 
ically prints out a string like this: 

PHPSESSID=07d696c4fd787cd6c78b734fb4855520 

Adding this to a link will pass the PHPSESSID variable via the querystring. Use 
something like this: 

<a href="mypage. php?<?=SID?>">cl i ck to page</a> 



<?= is shorthand forecho.You can use it any time you like, not just with 
sessions. 



If PHP is compiled with the -enabi e-trans-id option the session ID will auto- 
matically be added to every relative link. 

Basically, it is pretty simple. The following script will register a session variable 
named $my_var, and will assign it a value of "hello world". 

<? 

sessi on_start( ) ; 
session_register( "my_var" ) ; 
$my_var = "hello world"; 
?> 




84 



Part II: Working with PHP 



On subsequent pages the variable $my_var will be available, but only after you 
run the session_start( ) function. That function tells PHP to look for a session 
and if the session exists, to make all the session variables accessible as globals. 

It can take a little work with if statements to make your session variables prop- 
erly accessible. Look at the following short script for an example. 

<?php 

sessi on_start( ) ; 

sessi on_regi ster( "your_name" ) ; 

//check to see if $your name contains anything 

if ( ! empty ( $your_name) ) 

{ 

echo "I already know your name, $your_name"; 

) 

//this portion will probaby run the first time to 

//this page. 

el sei f (empty ( $your_name) && ! i sset( $submi t ) ) 

{ 

echo "<form name=myform methochpost acti on=$PHP_SELF> 
<input type=text name=f i rst_name> first name<br> 
<input type=text name=l ast_name> last name<br> 
<input type=submit name=submit val ue=submi t> 
</form>" ; 

//if the form has been submitted, this portion will 

//run and make an assignment to $your_name. 

) elseif ( i sset( Isubmi t ) && empty ( $your_name) ) 

{ 

$your_name = $first_name . " " . $last_name; 
echo "Thank you, $your_name"; 



After running this code, hit refresh on your browser. You will see that the script 
remembers who you are. 



Ti 


P^ 


am 


a ' 


% 


•3 



Your setcookie( ) and session_start( ) functions should always be at 
the very top of your file. If you try sending anything to the browser prior to 
setting a cookie, you will get error messages. 



Chapter 4: Getting Started with PHP -Variables 



85 



Using Built-in Variables 

There are a variety of variables set by your server and PHP environment. You can 
find a complete list of these by running phpinfo( ). If you haven't done it yet, go 
to your keyboard, type in the following, and then run the following script: 

<?php 

phpi nfo( ) ; 
?> 

It will deliver a page that looks like what you see in Figure 4-3. 



3 http://192.168. 1.1/info.php - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



Address |g] http://192.1G8. 1.1/info.php 



"31 t^Go |jLii 



■3 

Links »| 



PHP Version 4.0.0 




tip 



3 



System 


Linux mybox.mymachine 2.2.13-12 #1 Thu Dec 9 23:53:49 PST 1999 
i586 unknown 


Build Date 


May 22 2000 


Configure Command 


'./configure' '--with-apach.e=../apache_1.3.12' '--enable-track-vars' 


Server API 


Apache 


Virtual Directory Support 


disabled 


Configuration File (php.ini) 
Path 


/usr/local/lib 


ZEND_DEBUG 


enabled 


Thread Safety 


disabled 



iL 



J 



;astart| ^ *jj q> & ■ J; g| |]jg|eg| ±j|[lSVff>°Q'M<iy^> 3:53AM 



Figure 4- 3: phpinfo(); 



It's a good idea to delete this page when you're done with it. No need to give 
crackers any more information than absolutely necessary. 

You can use this variety of variables in a variety of ways. We'll take a look at 
some of these now, and show where and when you might use them. Some variables 
come from the PHP engine, while others originate from your Web server. 



86 Part II: Working with PHP 

PHP variables 

These are variables available through PHP. 

PHP_SELF 

This is the address of the file being run. Usually, the full path is given from the 
ServerRoot directory, which is very useful when a form is both presented and 
processed in the same PHP page. 

<? 

i f ( i sset($submi t ) ) 
{ 

//do some form processing here 
echo "thanks fon the submission"; 
) else ( 
?> 
<form name=myform method=post acti on=<?=$PHP_SELF?>> 

<input type=text name=f i rst_name> first name<br> 
<input type=text name=l ast_name> last name<br> 
<input type=submit name=submit val ue=submi t> 
</form> 
<? 
} 
?> 

Keep in mind that PHP_SELF always refers to the name of the script being exe- 
cuted in the URL. So in an include file, PHP_SELF will not refer to the file that has 
been included. It will refer to the script being run. 

It's worth noting that PHP_SELF behaves strangely when PHP is run on 
Windows or as a CGI module. Make sure to look at phpinfo( ) to see the value of 
$PHP_SELF on your system. 

HTTP_POST_VARS 

This is the array that contains all the variables sent through the POST method, usu- 
ally through forms. You can access each individual variable as an element in an 
associative array (for example $PHP_POST_VARS["myname"]). 

HTTP_GET_VARS 

This is the array that contains all the variables sent through the GET method. You 
can access each individual variable as an element in an associative array (for 

example $PHP_GET_VARS["myname"]). 

HTTP_COOKIE_VARS 

All of the cookies sent to the browser will be readable in this associative array. 
This includes the session cookie. If you are wondering how your cookies are 



Chapter 4: Getting Started with PHP -Variables 87 

behaving, phpinfo( ) will give you a quick readout of what your browser is send- 
ing to the server. 

Apache variables 

Apache keeps track of dozens of variables. We can't include a complete list of vari- 
ables here, as the variables you use will vary depending on your current setup. Here 
are some of the ones you might use frequently in your scripts. 

As you look at this list and phpinfo( ), keep in mind that if you are not getting 
what you want out of your Web server variables, you will need to make changes to 
your server configuration, not PHP. PHP just passes the information along and can- 
not alter these variables. 

DOCUMENT_ROOT 

This variable returns the full path to the root of your Web server. For most Apache 
users, this directory will be something like /path/to/htdocs. We use this variable 
throughout the book to make our applications portable. Take this include statement 
as an example: 

incl ude "$D0CUMENT_R00T/book/ functions /char set. php" ; 

By using the document_root variable instead of an absolute path, we can move 
the book directory and all its sub-folders to any other Apache server without wor- 
rying that the include statements will break. Keep in mind that if you are using a 
Web server other than Apache, DOCUMENT_ROOT may not be available. 



If you settheinclude_path directive in yourphp.ini file, you will not need to 
worry about specifying any path in your include statement — PHP will look 
through all of the directories you specify and try to find the file you indicate. 



HTTP_REFERER 

This variable contains the URL of the page the user viewed prior to the one he or 
she is currently viewing. Keep in mind when using http_referer that not every 
page request has a referer. If someone types the URL into a browser, or gets to your 
page via bookmarks, no referer will be sent. This variable can be used to present 
customized information. If you had a relationship with another site and wished to 
serve up a special, customized header for only those referred from that domain you 
might use a script like this. 

//check if my user was referred from my_partners_domain.com 
if(ereg ( "http.*my_partners_domai n . com.*" , $HTTP_REFERER) ) 




88 Part II: Working with PHP 



i ncl ude ' f ancy_header . php ' 
lei sej 

include'normal_header.phf 



Keep in mind that http_referer is notoriously unreliable. Different browsers 
serve up different http_referers in certain situations. It is also easily spoofed. So 
you wouldn't want to use a script like the preceding one to serve any secure infor- 
mation. I worked on a site where http_referer was used to determine if a special 
GIF should be included in the header. 

HTTP_USER_AGENT 

Anyone who has built a Web page knows how important browser detection is. 
Some browsers will choke on fancy JavaScript, and others require very simple text. 
The user_agent string is your key to serving the right content to the right people. A 
typical user_agent string looks something like this: 

Mozilla/4.0 (compatible; MSIE 5.01; Windows 98) 

You can then parse this string to get what you are looking for. 

You may be interested in PHP's get_browser( ) function. Theoretically, this 
function will determine the capabilities of your user's browser so you can find out 
if your script can safely serve out, for example, frames or JavaScript. The PHP 
manual has instructions for installation and use of get_browser( ), but I do not 
recommend using it. Why? Using get_browser( ) you will be told that both 
Internet Explorer 5 for the PC and Netscape Navigator 4.01 for the Mac support CSS 
(cascading stylesheets) and JavaScript. But as anyone with client-side experience 
knows, writing DHTM L that works on both of these browsers is a major task (and a 
major pain). The information you get from get_browser( ) can lead to a false sense 
of security. You're better off accessing http_user_agent and making decisions 
based on the specific browser and platform. 

REMOTE_ADDR 

This is the IP address of the user that sent the HTTP request. remote_addr is easily 
spoofed and doesn't necessarily provide information unique to a user. You might 
want to use it for tracking, but it should not be used to enforce security. 

REMOTE_HOST 

This is the host machine sending the request. When I dial it up through my ISP 

(att.net), the REM0TE_H0ST looks like this: 119 . san-f ranci sco-18-19rs . ca . 
dial-access.att.net. REMOTEJOST is often not available. 



Chapter 4: Getting Started with PHP -Variables 



89 



REQUEST_URI 

This is pretty much the same as PHP_SELF, except that it contains information in 
the querystring in addition to the script file name. It contains everything from the 
root path on. So if you were visiting http://www.mydomain.com/info/products/ 

index. php?id=6, request_uri will equal /info/products/index.php?id=6. 

SCRIPT_FILENAME 

This variable contains the filesystem's complete path of the file. 

Other Web server variables 

As mentioned earlier, phpinfo( ) is your friend. We developed applications for this 
book on Unix systems running Apache Web servers. But, as PHP will run on a vari- 
ety of operating systems and Web servers and MySQL does run on Windows as well 
as Unix, you should be aware of the different variables associated with whatever 
Web server and operating system you're running. 

You'll see that include files in our applications make use of the document_root 
Apache variable. If you were to attempt to move the application files to Windows, 
you would get an error in the include statements. The better choice when using 
Microsoft's Personal Web Server is the $appl_physical_path variable. 

Figure 4-4 gives a glimpse of some of the variables you can access from Personal 
Web Server. 



3 http:/.' local host'test.php - Microsoft Internet Explorer 


HEIE3 


J File Edit View Favorites lools Help K^l 


Address |g] http://localhosWest.php _^J (f^Go 


J Links » 




CONTENT_LENGTH 





J 

f 1 


HTTPS 


off 


INSTANCE ID 


1 


1 N STAN CE_M ETA_P ATH 


/LM/W3 SVC/1 


PATH INFO 


/test.php 


PATH TRANSLATED 


C:\lnetpub\wwwroot\test. php 


REMOTE_ADDR 


127.0.0.1 


REMOTE_HOST 


127.0.0.1 


REQUEST METHOD 


GET 


SCRIPT NAME 


/test.php 


SERVER_NAME 


localhost 


SERVER PORT 


80 


SERVER PORT SECURE 





SERVER_P ROTO COL 


HTTP/1.1 


SERVER SOFTWARE 


Microsoft-IIS/4.0 


URL 




t 


< 


r i 


m start | m^vQm^'A |jjs||ej£i ^ii^w$>°^%<fefflj^> am am 



Figure 4-4: Personal Web Server variables 



90 Part II: Working with PHP 



Testing Variables 



At the start of this chapter, we showed that assigning data to a variable determines 
the variable type. The appearance of the variable gives no indication as to what the 
variable contains. If you see $var sitting in a script you'll have no idea if this con- 
tains a string, an integer, a floating-point number, or an array. In fact, many times 
in your scripts you won't be sure if the variable contains a value, or even if it exists 
at all. For all these reasons, you need to perform tests. The following sections 
describe the types of tests you can perform. 

isset( ) 

This function tests whether a variable has any value, including an empty string. It 
returns a value of either true or false. If the variable has not been initialized or has 
not been set, issetO will test false. 

Consider the following script, which processes a MySQL query. You already 
know that database fields can contain both empty strings and null values. It's quite 
possible that in your script you would want to treat the two differently. To printout 
a special message when the query comes across null values, you would need to use 
issetc ) . In this code, $query is a select statement typed into a form element. 

Iresult = mysql_query ( $query ) or 
die (mysql_error( ) ) ; 

$number_col s = mysql_num_f i el ds( Iresul t ) ; 

echo "<b>query: $query</b><br>\n" ; 

//layout table header 

echo "<table border = 1 > \ n " ; 

echo "<tr al i gn = center>\n" ; 

for ($i=0; $i <$number_col s ; $i++) 

{ 

echo "<th>", mysql_f i el d_name( $resul t , $i ) , "</th>\n"; 
} 
echo "</tr>\n";//end table header 

//layout table body 

while ($row = mysql_fetch_row( $resul t) ) 

{ 

echo "<tr al i gn=l eft>\n" ; 

for ($i=0; $i <$number_col s ; $i++) 

{ 

echo "<td>" ; 

if ( ! i sset( $row[$i ] ) ) //test for null value 
(echo "NULL"; } 



Chapter 4: Getting Started with PHP -Variables 91 

el se 

(echo $row[$i ] ; } 
echo "</td>\n"; 
} 

echo "</tr>\n"; 
} 
echo "</table>"; 

Note that the exclamation point (!) means "not". So the phrase ifUisset 
($var) ) will test true if the variable is not set. 
If you wish to destroy a variable, use the unset function. 



emptyO 



The empty ( ) function overlaps somewhat with the isset( ) function. It tests true if 
a variable is not set, contains an empty string, or has a value of 0. It is useful for, 
among other things, processing form data. If you want to determine if the user put 
something in a text field you could use something like this: 

i f (empty ( $fi rst_name) ) 
{ 

echo "Please enter your first name. It is a required field"; 

exi t ; 



is_int() 

This tests whether a variable is an integer. It has two synonyms: is_integer( ) and 
is_long( ). You may need this function to troubleshoot a script when you're not 
sure if a variable is an integer or a string containing numerals. 

$a = "222"; 
$b = 22; 

Given these two variable assignments, is_int($a) would test false and 
is_int($b) would test true. 

is_ doublet) 

This function tests whether a variable is a floating-point (or double) number. It has 
two synonyms: is_float( ) and is^real ( ). 

isstringO 

This function tests whether a variable is a text string. 



92 Part II: Working with PHP 



i s_ array () 



This function tests whether a variable is an array. This is used frequently in the 
course of this book. A good example can be found in Chapter 6, in the discussion 

of the implode( ) function. 

is bool() 

This tests whether a variable is boolean, (contains either TRUE or FALSE). Note that 
the following examples are not boolean. 

$a = "TRUE"; 
$b = "FALSE"; 

In Chapter 6 you will see a variety of functions that return FALSE on failure. In 
these, FALSE is a boolean value. 



is objectO 



Returns true if the variable is an object. See Chapter 6 for a discussion of objects 
and object-oriented programming if you don't know what an object is. 



gettype( ) 



This function will tell you the type of variable you have. It will return the expected 
values (string, double, integer, array, or boolean), and it can also return types 
related to object-oriented programming (object, class). There will be more informa- 
tion on PHP object-oriented programming in Chapter 6. 

Note that gettypeO returns a string. So the following would test TRUE and 
print "Yes". 

$str = "I am a string"; 
$type = gettype( $str) ; 
if ($type == "string") 
{ 

echo "Yes"; 



Changing Variable Types 

There are three ways to change the type of any variable. 



Chapter 4: Getting Started with PHP -Variables 93 



Type casting 



By placing parentheses containing the variable type you require before the variable 
name, you will change the variable type. 

$a = 1; 

$b = (string) $a; 

echo gettype($a), "<br>\n"; 

echo gettype($b), "<br>\n"; 

This code would print, 

i nteger 
string 

Using this method you can cast a variable as an array, a double, an integer, an 
object, or, as in the preceding code, a string. 



Using settypeO 



This function takes two arguments. The first is a variable name. The second speci- 
fies the variable type. The advantage of using this function over casting is that 
settype( ) will return a value of FALSE if the conversion fails. And there is no way 
to detect a failed casting. It can take the same types as listed in type casting. 

$a = 1; 

settype( $a , "stri ng" ) ; 

intval(), doubleval(), and stringval() 

Finally, if you don't have enough ways to evaluate variable types, use one of these 
functions. They do not actually change the type of the variable, but return a value 
of the specified type. So in the following, you can be sure, $a will be treated like an 
integer. 

$a = "43"; 

$b = (intval ($a) * 2); 



Variable Variables 



PHP includes variable variables, which, in the wrong hands, could be used to write 
the most incomprehensible code imaginable. It enables you to take the contents of 
a variable and use them as variable names. Two consecutive dollar signs let PHP 



94 Part II: Working with PHP 

know to take the value of the variable and use that as a variable name. The follow- 
ing creates a variable name $f oo with the value of "bar": 

$a = ' foo ' ; 
$$a = ' bar ' ; 

In the context of a database application, variable variables might be used to cre- 
ate a series of variables against which you compare other variables. In the follow- 
ing, $fi rstrow is an associative array. 

$firstrow = array ( "f i rstname"=>" jay " , "1 astname"=>"greenspan" ) ; 

while ( 1 i st( $f i el d , $val ue) = each( $fi rstrow) ) 
{ 

Ifield = "fi rst_$field" ; 

$$field = $val ue; 
) 

echo $f i rst_f i rstname , " ", $f i rst_l astname ; 

When run through the while loop, the following variables would be created and 
printed. 

$fi rst_fi rstname = "jay" 

$fi rst_l astname = "greenspan" 



Summary 



If you read this chapter attentively (or even if you didn't) you should have a pretty 
good idea of how to work with PHP variables. 

PHP does a better job than any scripting language in making variables easy to 
access and process. If you want to get a feel of how PHP variables are used, take a 
look at Chapter 8, the first application in the book. There, many of the functions 
and concepts presented here are put to work. By flipping back and forth between 
this chapter and those scripts, you will see how variables are used and how scripts 
come together. 

One very important point: This chapter did not discuss variable scope, which is a 
very important topic. See Chapter 7, when we discuss functions, for an explanation 
of this topic. 



Chapter 5 

Control Structures 

IN THIS CHAPTER 

♦ Understanding the syntax of if statements 

♦ Determining true and false values with PHP 

♦ Learning PHP loops 

♦ Choosing loops to use in your scripts 



Control structures are the building blocks of programming languages. PHP has 
all of the control structures needed to make a language work. If you're familiar with 
C or Perl, none of the features we discuss in this chapter should come as much of a 
surprise. However, if you're approaching PHP from a background in VBScript or 
Visual Basic, the syntax will probably be different from what you're used to. If you 
find the syntax to be a little heavy at first, stick with it. You might find that the 
extra brackets and parentheses actually help you write readable code. 



The if Statements 



The if statement is pretty much the cornerstone of all programming languages. In PHP, 
an if statement typically takes this basic form: 

if (condition) 
t 

actions to perform if condition is true. 



After the word "if" there is a set of parentheses. Within those parentheses is the 
single condition or set of conditions to be tested. If the condition is evaluated as 
being true, the code within the curly braces will be executed. The following will test 
true and print "I'm True!" to a Web page. 

<?php 

$foo = 100; 

$bar = 10; 

if ($foo>$bar) 95 



96 Part II: Working with PHP 



echo "I'm True!"; 

} 
?> 

This is clear enough. But before we mention the complexities of the if statement, 
you should know how PHP determines whether a condition is true or false. 

Determining true or false in PHP 

The next section will show the operators commonly used in if statements. These are 
fairly intuitive. In the preceding code example, 100 is greater than 10, so it will test 
true. No problem. But there's a bit more to these tests in PHP. 
The words TRUE and FALSE also carry the expected meaning. 

if (TRUE) 
{ 

echo "Yup!"; //this will be printed 
} 

if (FALSE) 
{ 

echo "Nothing doing."; //this will not be printed 



But you're not limited to simple mathematical operators or the words TRUE and 
FALSE when testing a true or false condition. As you saw in Chapter 4, you will 
often test for the existence of a variable using isset( ) or empty( ). These functions, 
like many others in PHP, will return a value of if the condition is false, and a value 
of 1 if the condition is true. The following will actually print out "1". 

Imyvar = "I am setting a variable"; 
echo i sset( $myvar) ; 

In PHP "0" is equivalent to false. As you can guess, "1" is equal to true. But it's 
not just "1" that is true— any non-zero, non-empty value tests as true. This gives 
you some flexibility in your tests. 

When working with Web pages, you'll usually be doing some sort of text manip- 
ulation. Often you'll need to test whether the text string you're working with has a 
certain structure. For example, you might want to test if a string contains certain 
characters. You could use one of the regular expression functions for this, but you 
could also use the strstr( ) function. The strstr( ) function takes two arguments, 
both of them strings. It searches for the first occurrence of the string in the second 
argument in the first argument. It returns the string in the second argument plus all 
of the characters following that string. However, if the string isn't found, the func- 
tion will return FALSE. In the example below strstrO returns the "text string". 



Chapter 5: Control Structures 97 

$str = "my little text string"; 
strstr( $str , "text" ) ; 

Since the result of this function is not empty and not it could be used in a test. 
The following would test True and print out "Yeah!" 

$str = "my little text string"; 

if (strstr( $str , "text")) 

{ 

echo "Yeah!"; 



But, in the example below, the string is not found, so thefunction will return FALSE 
and nothing will print. 

$str = "my little text string"; 
$new_str = strstr($str, "nothing"); 
i f ( $new_str ) 
{ 

echo "nothing to print"; //this will not be printed 



This is a good place to note that the functions you create in the course of your 
programming will often need to return a TRUE or FALSE value. You can make your 
functions do this by returning TRUE or FALSE, or, if you prefer, 1 or 0. See Chapter 6 
for a rundown of functions if you don't know how to use them. Take a look at this 
example: 

//tests whether a variable starts with "http://" 

function url_test ($url) 

{ 

if (strtol ower( substr( $url ,0 , 7 ) )== "http://") 
{ 

return TRUE; //this could also be 1 
1 
el se ( 

return FALSE; //could be 



$myurl = "http://www.theonion.com"; 

if (url_test ( $myurl ) ) 

{ 

echo "Thanks for entering a valid URL."; 



98 



Part II: Working with PHP 



Comparison operators 



There aren't too many comparison operators in PHP. Table 5-1 lists them. 



Table 5-1 PHP'S COMPARISON OPERATORS 



Symbol Operator 

== (2 equal signs) equal to 

=== (3 equal signs) identical to 



not equal 
greater than 

less than 

greater than or equal to 

less than or equal to 



Description 

Determines if two quantities are equal. 

Determines if the two values are 
of the same value and the same 
variable type. 

Determines if the values are not equal. 

Determines if the value to the left of 
the symbol has a higher value than 
the one to the right of the symbol. 

Determines if the value to the left of 
the symbol has a lower value than 
the one to the right of the symbol. 

Determines if the value to the left 
has a higher or equal value to the 
one on the right. 

Determines if the value to the left 
has a lower or equal value to the 
one on the right. 



Logical operators 



In addition to comparison operators, you will be using logical operators in your 
scripts. Table 5-2 lists the logical operators. 



Table 5-2 PHP'S LOGICAL OPERATORS 



Symbol example 



and 



if ($a ==0 and $b==l) 
if ($a ==0 && $b==l) 



Description 

Checks both conditions. 

Same as the previous row, but has a 
higher precedence (see Note below). 



Chapter 5: Control Structures 



99 



Symbol example 



or 



xor 



if ($a ==0 or $b ==i; 



if ($a ==0 | | $b ==1) 



if ($a ==0 xor $b==l! 



if ( !empty($a) ) 



Description 

Determines if one or the other meets 
the condition. 

Same as the previous row, but has a 
higher precedence (see Note below) 

This is known as "exclusive or". It 
determines if one of the two is true but 
not both. If both of these conditions are 
true, the overall test will be false. 

Determines if something is not the case. 
In this example the condition will be true 
if $a has a value. 




The difference between && and and is their order of precedence. PHP 
must determine which operators to compare first. It does this based on the 
list found at http://www.php.net/manual/language.operators. 
precedence . php. 



Complex if statements 



Using the operators in Table 5-1 and 5-2 you can create if statements that area bit 
more complex than the basic one at the beginning of this chapter. Here are a few 
quick examples: 

if ($var == 1 && $var2 < = 5 && ! empty ( $var3) ) 
( 

//do some stuff 



Since this is a book dealing with MySQL databases, we'll show some examples of 
if statements you can use when playing with database queries. 

To test if a select query returned any rows, you could use either of the following: 

$query = "select * from my_table"; 
$result = mysql_query ( $query )or 

di e(mysql_error( ) ) ; 
if (mysql_num_rows( $resul t ) >0) 



100 Part II: Working with PHP 

//do something here. 
} 

//this would also work... 
if (!$row = mysql_fetch_array ( $resul t) ) 
{ 

echo "there were no rows to fetch, so the query must have 
returned no rows . " ; 



This will test if an update query actually changed anything. A similar construct 
would work for update and delete statements. 

Iquery = "update mytable set col 1=' my text' where id = 1"; 
mysql_query ( $ query ) or 

di e(mysql_error( ) ) ; 
if (mysql_affected_rows( ) == 0) 
{ 

echo "query did nothing"; 
} 

if ... else statements 

If you're clear on the previous sections, there's nothing here that will surprise you. 
The else portion of an if ... else statement allows you to specify code that is executed 
if the condition specified is false. 

$a = 2; 

if ($a == 1) 

{ 

echo "it's equal"; 
) else ( 

echo "i t i s not equal " ; 
} 

This code will print "it is not equal". 

if ... elseif statements 

You will often have to check a variable against more than one set of conditions. For 
instance, you might have a single page that will insert, edit, and delete records from 
a database. It would be fairly typical to indicate which portion of the script you 
wish to run by assigning different values to a submit button in an HTML form. 
When the form is submitted, the value of the submit button can be checked against 
several elseif statements. 

if ($submit == "edit") 



Chapter 5: Control Structures 101 



// code for editing database 
( elseif ($submit =="update") 

//code for updating records 
lelseif ($submit == "delete") 

//code for deleting records 
( el se 

echo "I have no idea what I should be doing."; 



"elseif "is not that same as "else if'.'lf you have that space between the words, 
you will not get an error, but you may get some weird behavior. 



Ti 

ism 


P# 


% 





Alternative if... structures 

There are a couple of different ways to write if statements. The first simply substi- 
tutes a colon for the opening curly brace and the word endif with a semicolon for 
the closing curly brace. 




This syntax is depreciated.You're better off not using it. 



if ($a==l): 

echo "I knew a was equal to one."; 
elseif ($a>l): 

echo "a is bigger than I thought."; 
el se: 

echo "a is a little number."; 
endif; 



The other alternative if structure we have is what's known as a trinary operator. 
It's essentially a shortened form of an if ... else statement and we'll use it in this 



102 Part II: Working with PHP 

book to save a few lines of code when there's a simple assignment of a variable to 
be done. It looks like this: 

$a = ($x==l) ? "x was one" : "x wasn't one"; 

The portion before ? is the condition to be tested (here, is x equal to 1). If the 
condition is true, the portion between ? and : is carried out ($a is assigned the string 
"x was one"). If not, the expression in the third portion, between : and ; will be 
executed and $ a will carry the string "x wasn't one". 



switch ... case 



The switch structure is an alternative to using multiple if ... elses. This won't work for 
everything, but in some situations switch will help you remove some ugly syntax. 

Choose a variable against which you wish to run a comparison. Continuing the 
example given in the discussion of if... else, we may wish to execute different parts 
of script based on the value passed by a submit button. 

switch (Isubmit) 
{ 

case "insert": 

// code to insert to database 
break ; 
case "update": 

//code to update database 
break; 
case "display": 

//code to display 
break; 
} 

Here the code tests against the value in $submit. In the case that $submit is 
equal to "insert", that portion of code is run. 

Note the use of break above. If break is not included the code will continue to 
run. For example, the if isubmit was equal to "update" the following would run 
the code for both the update and display portions: 

switch (Isubmit) 
{ 

case "insert": 

// code to insert to database 
break ; 
case "update": 

//code to update database 



Chapter 5: Control Structures 103 



case "display": 

//code to display 
break; 



Loops 



No matter what language you've used in the past, you'll know that loops are an 
essential part of programming. PHP has as rich set of loops that should satisfy your 
every programming need. 

while... 

This is probably the most common loop, therefore we'll discuss it first. You will give 
the while loop a condition to validate. As long as that condition is true, the code 
within the curly braces will be executed. 

while (condition) 
{ 

code to execute here; 



For a very basic example, the following would print all the numbers between 
and 10: 

$a = 0; 

while ($a<=10) 

t 

echo "$a <br> \n"; 
$a++; 



For something a bit more practical, you will use a while loop to iterate through 
every row returned by a database query. Since mysqi_fetch_array( ) will return 
FALSE if there's no row to be fetched, it works quite nicely with a while... loop. 

$query = "select fname, Iname from people"; 
$result = mysql_query ( $query ) or 

di e(mysql_error( ) ) ; 
while ($row = mysql_fetch_array ( $resul t ) ) 
{ 

echo $row["fname" ] , " " , $row["l name"] , "<br> \n"; 



104 



Part II: Working with PHP 



USING WHILE WITH LIST() = EACH() 

Another place while... often comes into play is with arrays, when using the l ist( ) 
= each( ) structure. This structure assigns elements in an array to named variables. 
It will iterate through the array, and when there are no more elements to pull from, 
it will test FALSE, and the while loop will stop. When pulling from an array, l ist( ) 
is expecting an associative array and will take two variables: the first for the key, 
the second for the value. 

Iknicks = array (center => "Ewing", point => "Childs", 

shooti ng_guard => "Houston", 

forward => "Sprewell", strong_forward => "Johnson" 

); 
echo "<h2>The Knicks 1999 Starting Five Were</h2>"; 
while ( 1 i st( $key , $val ue) = each ($knicks)) 
{ 

echo "$key: lvalue <br>\n"; 




After running the preceding code the array pointer will be at the end of the 
array. If you wish to loop through it again, you will have to move the pointer 
to the beginning of the array with reset. In the preceding example, reset 
(Sknicks) would work. 



Note that if you don't have an associative array and you wish to grab array values, 
you will need to account for it in your l ist( ). Do this by including a comma within 
the list parentheses. 



$names = array ( "John" , "Jacob", "Jason", 
while (list ( , lvalue) = each ($names)) 
{ 

echo "lvalue <br> \n"; 



'Josh'' 



If you didn't have the comma preceding $vai ue, the ordinal placement of each 
element would be assigned to value and the code would print "0, 1, 2, 3". 

If you want to just get the keys out of an associative array, your list statement 
should contain something like l ist($key , ). 

List is also useful with mysqi_fetch_array( ). It can be kind of a pain to keep 
referring to values by their associative array reference (e.g., $row["fi rst_name"]). 



Chapter 5: Control Structures 105 

If you use l i st( ) = each( ), you won't have to assign each record to a variable 
and then reference it as an associative array. The following works just fine: 

Squery = "select fname, Iname from users"; 
$result = mysql_query( Squery ) or 

di e(mysql_error( ) ) ; 
while (list ($fname, $lname) = mysql_fetch_array ( $resul t) ) 
t 

echo $fname . " ". Ilname . "<br>\n"; 



As you saw above, listQ has a couple of uses. Though we're stressing its use with 
the eachQ statement, it can generally bethought of as an "array destructor". That is, 
it pulls elements out of an array. Similarly, each( ) is an "array iterator", it walks 
through all of the elements in an array, and it doesn't need to be used with i ist( ), 
though that is by far the most common usage. 

Continuing with the subject of while loops and mysqi queries, you will probably 
need a quick piece of code that will print out the results of any query. For this, you 
can use a nested set of while loops. The outer loop fetches each individual record 
from the database. The inner one prints out the contents of each individual record. 

whiledrow = mysql_fetch_array ( Iresul t , MYSQL_ASSOC) ) 
{ 

while (list($key, lvalue) = each ($row)) 

t 

echo "<b>$key : </b> lvalue <br>\n"; 



Note the use of mysql_assoc. If you didn't use this, mysqi_fetch_array would 
return every column twice, once with ordinal reference and once with the associa- 
tive key. 

ALTERNATIVE WHILE... SYNTAX 

If you wish, you can write a while loop like this: 

while (condition): 

//code here 
endwhi 1 e ; 




This is also deprecated.You are better off not using it. 



106 Part II: Working with PHP 

do ...while 

The do. ..while loop is nearly identical to the while loop discussed above. The only 
difference is that the condition is tested after the code in question has been run once. 

do 

{ 

//code to be used here. 
) while (condition); 

This structure may be useful to you. It may even be vital to scripts you need to 
write. But in the course of writing seven applications for this book, we didn't need 
to use it once. 

for 

The for loop takes three expressions. The first is evaluated only the first time 
through the loop. The second argument is a condition that is evaluated each addi- 
tional time through the loop; if the condition in the second argument tests false, the 
loop will end. The third expression will be executed in every loop after the first. 

As an example, the following would iterate through every value in an array and 
print the value for each element. 

$myarnay = arnay (jay, brad, John, kristin); 
for ($i = 0; $i < count( $myarray ) ; $i++) 
{ 

echo $myarray[$i ] . "<br>\n"; 



The first time through, $i is assigned the value of 0, so the first element in the 
array will be printed. The next time and each subsequent time through, $i will be 
incremented by one. The loop will end as soon as $i is equal to the length of the 
array (which would be 4). Remember that the elements in the array start at 0, so the 
last element in the above array is $myarray[3]. 

You can also leave any of the three expressions in the for loop empty. If you 
leave the second expression empty, the if condition will evaluate to true, and you 
will need to make sure that your loop will eventually hit a break statement (break 
will be discussed soon). The following would be very bad: it would run indefinitely, 
using up your memory and CPU. You'd have to kill the Web server to get this script 
to stop. It could bring your entire machine down. 

for ($i = 0; ; $i++) 
{ 

echo " $ I <br>\n"; 



Chapter 5: Control Structures 107 

There are occasions when leaving the second expression empty serves a purpose. 
But again, this is something that will not come up in the course of the applications 
presented in this book. 

The following is an alternative structure for the for loop (this is probably starting 
to look a bit familiar). This is also deprecated and shouldn't be used: 

for ($i=0; $ i < 1 ; $i++): 

//run code here 
endfor ; 

foreach 

Theforeach structure is used exclusively with arrays. If you prefer, you can use it in 
place of listo = eacho on most occasions. This structure will work from the 
beginning to the end of an array, assigning each element to a scalar variable you 
indicate with the word as. The following would print all the values in the array 

$names_array. 

$names_array = array("jay", "brad", "ernie", "bert"); 

foreach ( $names_array as $first_name) 

{ 

echo $first_name; 



If you are working with an associative array, you will likely need to access both 
the key and the value of every array element. The following syntax will achieve this. 

$jay_info = array (fname => "jay", Iname => "greenspan", wife => 
"melissa", hobby =>" juggl i ng" ) ; 
foreach( $jay_i nf o as $key => lvalue) 
{ 

echo "<b>$key : </b> lvalue <br>\n"; 



There is no good reason to recommend either l ist( ) = each( ) or foreach( ). 
They both do the same thing for arrays. Choose whichever you think looks best on 
your PHP page. 



We used 1 i st( )=each( ) in the applications in this book, mostly because 
it was available when we were writing code in PHP3 and foreach() wasn't 
available. 




108 Part II: Working with PHP 

continue and break 

Within loops you may need to either break out of the loop entirely or skip to the 
next item to be addressed in the loop. For these situations, you can use continue and 
break, respectively. 

continue 

Consider a situation when you're reading from the file system and you would like 
your script to address each file in a specific directory, but we have no need to 
address any subdirectories. When PHP reads names from the directory, you don't 
know if the item is a file or directory, so you need to run a test using the is_dir( ) 
function. We'd want to skip over listings that are directories. The script looks some- 
thing like this: 

$di recto ry=opendir(' /home /jay/'); 

echo "Files are:<br>\n"; 

while ($file = readdi r( $di rectory ) ) 

{ 

if ( i s_d i r ( $ f i 1 e ) ) ( c o n t i n u e ; ) 

echo " $ f i 1 e <br>\n"; 
//process files here; 



closedir($di rectory); 



?> 



Note that continue isn't necessary here. You could also code this script like this, 
and some feel this a better way of going about it. 

$di recto ry=opendir(' /home /jay/'); 

echo "Files are:<br>\n"; 

while ($file = readdi r( $di rectory ) ) 

{ 

if ( !is_dir($file)){ 

echo "$f i 1 e <br>\n" ; 



closedir($di rectory); 

break 

Break will release the script from a control structure but will continue the execution 
of a script. It is almost always better to avoid using break, i f statements can accom- 
plish the same thing and make for cleaner code. 



Chapter 5: Control Structures 109 



Including files 



Including files in your PHP scripts is vital to writing good code. And technically, 
the functions for including files (incl ude and requi re) are not control structures, 
they are language constructs. They are discussed in detail in Chapter 7. 



Summary 



In this chapter you saw the building blocks of the PHP language. You saw how 
to make use of loops and if blocks. If you read Chapter 4, where variables were 
discussed, you now know all of the basics needed for programming with PHP. 

Coding is all about working with variables, loops, and if blocks. The various 
combinations of these will take care of everything you will need to accomplish 
in your applications. However, there is still one major portion you need to learn: 
functions. Chapter 6 shows how PHP's built-in functions operate on your scripts. 



Chapter 6 

PHP's Built-in Functions 



IN THIS CHAPTER 

♦ Using PHP's built-in variables 

♦ Handling strings 

♦ Working with arrays 



PHP has an amazing number of built-in functions. Many are only available to 
you if PHP is compiled with certain options. If, for example, you need to do some 
XML parsing, PHP has two function sets that can help you. (One uses a SAX 
approach, the other a DOM approach). If you need LDAP, IMAP, or PDF functions, 
there is a function set for you. Additionally, PHP has an API (application program 
interface) for about every relational database on the planet. But really, there's no 
need to cover most of these functions in this book. 

Another thing to keep in mind is that the function set is changing almost daily. 
PHP 4 is internally structured in a way that makes it extremely easy for program- 
mers to add additional functions. In fact, if you know your way around C, you 
could probably add a new function into PHP in a few hours. So you can expect reg- 
ular additions to the core function set. 

Your best friend, as always, is the online PHP manual: http://www. 
php.net/manuai. It's is the only source where you can be sure that the list of func- 
tions will be more or less up to date. If you want to go directly to the explanation 
of a function, all you need to do is point your browser at http://www.php.net/ 

f uncti on_name. 

We want to point out one more thing before we get started here. There are seven 
applications in the final two portions of this book. In the course of creating these 
functions, we made use of a little over 100 of PHP's built-in functions. So while 
there are thousands of built-in functions, you will probably only make regular use 
of a relatively small number. 



A pretty neat resource is the function table at: http: //www.zugeschaut- 
und-mitgebaut.de/php/. 




Ill 



112 Part II: Working with PHP 

Function Basics 

Functions all take the same basic form. 

return_type f uncti on_name(argumentl , argument2, argument3) 

First there is the function's name; note that the name of the function is not case- 
sensitive. However, we don't know of any programmer who ever uses uppercase letters 
to refer to a built-in function. 

Next there is a set of parentheses. Every function will have a set of parentheses 
marking the beginning and end of the arguments. 

Arguments 

So what's an argument? An argument is simply a value that the function is expect- 
ing. Depending on the purpose of the function, it may expect zero, one, two, three, 
or more arguments, and any of the arguments may be any variable type— maybe a 
string, maybe an integer, or maybe an array. To give you a better idea of what argu- 
ments are, let's look at a very useful function for string handling. 

The str_repiace( ) function is extremely helpful. Let's say you had the follow- 
ing string: 

$str = "My name is Jay . " ; 

Say that in the $str variable you need to replace "Jay" with "John". Within $str 
you need to search for "Jay" and replace it with "John". Here, you would expect a 
function to take three arguments: the string to be searched for, the replacement 
string, and the string to be searched through. It so happens that in PHP, the argu- 
ments come in this order: 

str_repl ace( stri ng to search for, replacement string, string to be 
searched through) ; 

Or to put it in practice: 

$str = "My name is Jay . " ; 

$new_str = str_repl ace( " Jay " , "John", $str); 

Keep in mind that certain functions will have optional arguments and a few will 
take no arguments at all. The substr( ) function, for example, has an optional third 
argument. This function returns a portion of a string by its ordinal references. To 
get everything from the second character to the next-to-last character, you would 
use the following: 

//note, the first character in the string is 
$new_str = substr ( $str_var , 1 , -1 ) ; 



Chapter 6: PHP's Built-in Functions 113 

However, to get everything from the second character on, you would use the fol- 
lowing: 

$str = substr ( $str_var , 1 ) ; 

So in this function the third argument is optional. (We'll point out optional argu- 
ments as we move through the functions.) The details of working with substr( ) 
will be covered later in the chapter. 

There are also a few occasions when a function will take no arguments at all. A 
good example of this is phpinfo( ). It spits out everything you need to know about 
your PHP environment without taking any arguments. Another good example is 
timet ), which returns the current Unix timestamp. 

Return values 

The final thing you should be aware of is what the function will return. In the 
above case, str_replace() will return a string. What you do with this string is 
your business. You could assign it a variable or print it out, or do whatever else 
seems appropriate. 

//assign to variable 

$new_str = str_repl ace( " Jay " , "John", $str); 

//print directly 

echo str_repl ace( " Jay" , "John", $str); 

Note that functions may return arrays, integers, doubles (floating-point num- 
bers), objects, or sometimes Boolean values. In Chapter 5, you saw a good example 
of a function that returns a Boolean value (that is, TRUE or FALSE). If you want to 
determine whether a variable is an array, you can use the is_array( ) function. 

if ( i s_array ( $var) ) 
{ 

//process array 



There are also functions that will return a value if there is a value to be returned, 
and that will return FALSE if there is no value to be returned. A good example of 
this is the mysqi_fetch_array( ) function. This function will grab rows from a 
result set returned by a query as long as there are results to grab. When there are no 
more rows to be had, it returns FALSE. As you saw in Chapter 5, this is very help- 
ful for looping through all rows returned by a query. 

$result = mysql_query ( "sel ect * from my_table") or 
die ( my s q 1 _e r r o r ( ) ) ; 



114 Part II: Working with PHP 

while($row = mysql_fetch_array ( $resul t) ) 
{ 
//process row 



Finally, there are occasions where a function will return nothing. This will be 
common in functions that perform a specific action, like closing a connection to a 
database or the file system. 



Function Documentation 

As we say repeatedly throughout this book, the PHP online manual is your friend. 
The documentation team is amazing, and we really believe the quality of the online 
manual is one of the reasons for the success of the language. As there is no way we 
can cover every PHP function in this book, you will need to consult the manual. 
For that reason, we want to take a minute to go over how the functions are pre- 
sented in the manual. 

A typical manual reference will look something like this: 

int mysql_af fected_rows ([int 1 i n k_ identifier]) 

This function returns the number of rows affected by an update, insert, or delete 
query. Looking at this, you can see that the first portion (int) indicates the variable 
type that will be returned. This could be any of the variable types or void (meaning 
that the function will return nothing). Then within the parentheses there will be a 
list of arguments. The type of argument is listed as well as what it represents. Note 
that optional arguments are placed in brackets. So above, the function requires no 
arguments but has one optional argument: the connection identifier grabbed from 
mysqi_connect( ). In a case like phpinfo( ), you will see that the argument list 
is void. 

In the preceding example, if you pass an argument, it better be an integer. If you 
were to give it a string or an array, you will get an error. 

Important PHP 4 Functions 

In this section, we will attempt to break down PHP 4 functions into logical group- 
ings. Along the way we will cover every function used in the applications presented 
in this book. 



Chapter 6: PHP's Built-in Functions 



115 



MySQL API 



There are a total of 33 MySQL functions available in PHP. Only 17 of these are used 
in the applications in this book. You may find uses for some of the other MySQL 
functions in your applications, but you probably won't use all of them. For the sake 
of this listing, I'll break the functions into the set you might use the most, and then 
the ones that you're less likely to use extensively. 

FREQUENTLY USED MYSQL FUNCTIONS 

You will probably end up using the following functions frequently. You may want 
to dog- ear this page. 

MYSQL_CONNECT() You can't do anything with MySQL until you make the con- 
nection using the following function. 

int mysql_connect( str host, str username, str password) 

Most often you will be connecting to MySQL on localhost using a username and 
password assigned to you, the Web developer. The integer that this function returns 
to you is a connection identifier. You may need to track the connection identifier 
and use it with the mysql_db_select( ) function. It will typically look something 
like this: 



fconn = mysql_connect( "1 ocal host" , "username" 
die ("Could Not Connect to Database"); 



"password") or 




If MySQL is not installed on a standard port or if the mysql socket is not 
located in /tmp/mysql.sock, you can specify the port of socket location in 
the host string. For example: 

mysql_connect("localhost:/usr/local/mysql.sock", 
"username", "password"); 

Or, if the MySQL database in sitting on another machine, you can access it 
with the following 

mysql _connect( "mymachi ne.mydomai n . com" , "username" , 
"password" ) ; 



You can also specify host, username, and password in the php.ini file. That 
way you could leave one or more of these arguments empty. 




116 Part II: Working with PHP 

MYSQL_PCONNECT() The mysql_pconnect( ) function works exactly like mysql_ 
connecto but with one important difference: The link to MySQL will not close 
when the script finishes running. 

int mysql_pconnect( str host, str username, str password) 

When you use this function the connection remains open, and additional calls to 
mysqi_pconnect( ) will attempt to use these open connections when they run. This 
could make your scripts quite a bit faster. 

It is interesting to note what happens when mysql_pconnect( ) is run. The first 
time the script is run, PHP will ask for a connection, and MySQL will open a connec- 
tion to the database. When that script finishes, the connection remains available. The 
next time a PHP page is requested, PHP will ask for a connection that is already open. 
If MySQL has one available, it will grant PHP the open connection. If there are no 
open connections available, a new connection will be opened. 

Establishing a connection with the MySQL database will be about the slowest 
function in your scripts. If PHP can use a connection that has already been opened, 
there will be far less overhead in the application. 

In order for mysql_pconnect( ) to work, set the following lines in your php.ini file: 

mysql . al 1 ow_persi stent = On 

mysql .max_persi stent = -1; maximum number of ;persistent 

1 inks. -1 means no 1 i mi t 

Note that these are the defaults. You will probably want to limit the number of 
persistent connections if you use this method. 

MYSQL_SELECT_DB() The mysql_select_db( ) function changes the focus to 
the database you wish to query. 

int mysql _sel ect_db (string database_name [, int 1 i nk_i denti f i er] ) 

You can include the integer it returns in the mysql_query( ) function, but it is 
only really needed if you are connecting to more than one database. The second, 
optional argument is the link identifier retrieved from the mysql_connect( )/ 
mysql_pconnect( ) function. It typically looks like this: 

$db = mysql_sel ect_db( "database_name" ) or 
die ("Could Not Select Database"); 




See the description ofthe db_connect( ) function in Chapter8to see how 
to handle connections to the MySQL server and a specific database in a sin- 
gle function. 



Chapter 6: PHP's Built-in Functions 117 

MYSQL_QUERY() This mysql_query( ) function is probably the MySQL function 
that you will use most frequently in your scripts. 

int mysql_query (string query [, int 1 i nk_i denti f i er] ) 

This function sends any query that you can put together to MySQL. It is impor- 
tant to understand that this function does not actually return the result of the 
query. It opens a cursor that points to the result set on MySQL. So if you were to do 
the following: 

echo mysql_query ( "sel ect * from table"); 

you would not get a meaningful answer, only the number that identifies the result 
set. Following mysqi_query( ), you will need to make use of one of the functions 
that actually retrieves the data from MySQL (mysql_fetch_row( ), mysql_fetch_ 
array( ), mysql_result( )). 

The optional second argument would be the result of either mysqi_connect( ) or 
mysqi_seiect_db( ). It is typically used as in the following code sample. Note that 
a query can fail for any number of reasons. It is best to use mysql_error( ) to find 
out why the query failed. 

$result = mysql_query ( "sel ect * from db") or 
die ( my s q 1 _e r r o r ( ) ) ; 



See the discussion of the safe_query( ) function in Chapter 8 to see how 
to safely handle queries with a uniform function. 



MYSQL_FETCH_ARRAY() Once you have retrieved your result from a query, you 
will (more often than not) use mysql_fetch_array( ) to retrieve the rows from 
a query. 

array mysql_fetch_array (int result [, int resul t_type] ) 

This function returns an associative array, with names of the select columns as 
the key. By default, mysql_fetch_array( ) will return each column in a row twice: 
the first will have an associative key, the second will have a numeric key. To tell 
PHP to limit the results to numeric results use MYSQL_NUM as the second argu- 
ment. To get only the associative keys, use MYSQL_ASSOC as the second argument. 

This function returns FALSE when there are no rows left to fetch. 

The following will print the results of a query as a table: 

$query =("select * from tabl e_name" ) ; 




118 



Part II: Working with PHP 



Iresult = mysql_query ( $query ) 

or die ( echo mysql_error() ); 

echo "<table>"; 

//if I don't use MYSQL_ASSOC or MYSQL_NUM, each row wi" 

//be retrieved twice, and I don't want that 

while ($row = mysql_fetch_array ( Iresul t , MYSQL_ASSOC) ) 

{ 

echo " < t r > " ; 

whilet list ($key, lvalue) = each($row) ) 

{ 

echo "<td>" . lvalue . "</td>"; 
) 

echo"</tr>" ; 
} 
echo "</table>"; 




In Chapter 3 there is a script that prints any query to a table that includes the 
column names as table headers. 



MYSQL_FETCH_ROW() The mysql_fetch_row( ) function works almost exactly 
like mysqi_fetch_array( ), but it only returns a numeric array of the fetched row. 

array mysql_fetch_row (int result) 



There's generally little reason to usemysql_fetch_row( ),and we recom- 
mend that you usemysql_fetch_array( ) instead. However, you will see 
many scripts that use this function. 




MYSQL_INSERT_ID() Frequently the primary key of a MySQL table will be an 
autojncrement field. In such cases, after you do an insert query you may need to 
know the number MySQL assigned to the newly inserted row. 

int mysql_i nsert_id ([int 1 i nk_i denti f i er] ) 

We use this function used many times throughout the book; one example is in 
Chapter 12 in the discussion of the admin_user.php page. 

You might think that the following method would work equally well for getting 
the row that was just inserted into the database. 



Chapter 6: PHP's Built-in Functions 119 

mysql_query ( "i nsert into users (fname, Iname) values ('jay', 
' greenspan ' ) or 

die (my si q_error( ) ) ; 
mysql_query ( "sel ect max(user_i d) from users"); 

However, there is no guarantee that this script will return an accurate result. On 
a busy server, it is possible that an insert (perhaps run by another users accessing 
the script at nearly the same time) will occur between the time it took for these two 
queries to run. In such cases, your user will end up with bogus data. 

mysqi_insert_id( ) returns the value of the autojncrement field associated with 
the specific copy of the script, so you know the number that it returns is accurate. 

MYSQL_NUM_ROWS() A query can execute successfully, but still return zero 
rows in the result. This function will tell you exactly how many rows have been 
returned by a select query. 

int mysql_num_rows (int result) 
You might use it in a case like this: 

$query = "select * from table_name"; 
$result = mysql_query ( $query ) or 

d i e ( my s q 1 _e r r o r ( ) ) ; 
if (mysql_num_rows( Iresul t ) == 0) 
( 

echo "Sorry, no results found."; 
} el sel 

//print results 



MYSQL_AFFECTED_ROWS() This function is similar to the mysql_num_rows( ) 
function, but works for a different set of queries. It returns the number of rows in a 
table that are affected by an update, insert, or delete query. 

int mysql_affected_rows ([int 1 i nk_identi f i er] ) 

This function is excellent for checking that a query you have run has actually 
accomplished something. 

$query = "delete from table_name where unique_id = 1"; 
$result = mysql_query ( $query ) or 

die ( my s q 1 _e r r o r ( ) ) ; 
$del eted_rows = mysql_af fected_rows( ) ; 
if ( $del eted_rows == 0) 
( 

echo "no rows removed from the table."; 



120 Part II: Working with PHP 



) else { 

echo "You just removed $del eted_rows row/rows from the 
database."; 



MYSQL_ERRNO() If there is a problem with a query, this function will spit out the 
error number registered with MySQL. 

int mysql_errno ([int 1 i nk_i denti f i er] ) 

On its own, this isn't terribly helpful. For the most part, you would only use this 
if you wished to use custom error handling. Better error messages come from mysql_ 
errorQ which is discussed next. 

MYSQL_ERROR() This function should accompany every mysql_query( ) you run. 

string mysql_error ([int 1 i nk_i denti fi er] ) 

As you can see in code samples throughout the book we make use of mysql_ 
error with the die statement. 

mysql_query ( "sel ect * from my_table") or 
die (mysql_error( ) ) 

Without it, you will only know that your query has failed. You won't know if 
you're searching for a database that doesn't exist or if you're trying to insert a 
string into a numeric field, or have made some sort of syntactical blunder. 

MYSQL_ RESULT!) This function, which grabs the contents of a specific cell, should 
be used sparingly. 

mixed mysql_result (int result, int row [, mixed field]) 

It's relatively slow and can almost always be replaced by mysqi_fetch_array( ). 
However, if you need to grab contents from a single cell it can be convenient. The 
second argument will always be the number of the row you are accessing. The third 
can either be the numeric offset of the column or the column name. 

Here's an example of how you could use mysqi_resui t( ). In this case, we're 
running a simple count( ), so there is only one value to be accessed. Using mysqi_ 

result( ) is a bit easier than mysql_fetch_array( ). 

mysql_connect( "1 ocal host" , "username", "password"); 

mysql_select_db("test"); 

$ result = my sql_query(" select count (*) from users") or 

die ( mysql_error( ) ) ; 
echo mysql_resul t( $resul t , ,0) ; 



Chapter 6: PHP's Built-in Functions 121 

If you have many rows, or even many columns, that need to be retrieved you 

should use mysql_fetch_array( ). 

LESS FREQUENTLY USED MYSQL FUNCTIONS 

Given the title of this book, it wouldn't make too much sense if we didn't cover the 
entire API. There are many available functions that probably won't come into play 
too often. But if you're going to be writing applications with these tools, it's best to 
know what's available. 

MYSQL_FETCH_OBJECT() This function provides another way to access data 
retrieved from a query. 

object mysql_fetch_object (int result [, int result_typ]) 

This grabs a row just like mysql_fetch_array( ). The only difference is that you 
refer to values fetched from a row as object properties. It can also take the constants 
MYSQL_ASSOC, MYSQL_NUM, or MYSQL_BOTH. 

$result = mysql_query ( "sel ect distinct fname, Iname from users where 
id=l") or 

die ( my s q 1 _e r r o r ( ) ) ; 
$an_object = mysql_fetch_object( $resul t ) ; 
echo "First name: " . $an_object->fname; 
echo "Last name: " . $an_object->l name ; 



We discuss the object-oriented approach in Chapter 7. 




There are two reasons you might want to use this function. First, you love work- 
ing with 00 syntax and wish to extend it to your database calls. The second reason 
may be relevant to the Perl hackers out there. 

PHP 4, like Perl, allows for Here printing, which we discussed in Chapter 4 under 
the discussion of delimiting strings. 



"Here"printing may not work on Windows installations. 



Ti 

am 


P# 


% 





122 Part II: Working with PHP 

When using Here printing, you can print only simple variables— no arrays. 
However, object properties are allowed. Thus, the following would work: 

print <<<E0Q 

field value is $an_object->fname ; 

EOQ; 

MYSQL_FREE_RESULT() This function frees result memory used by a query. 

int mysql_f ree_resul t (int result) 

Usually you won't need this function, as connections to MySQL are automati- 
cally closed after a script executes. If you run some sort of query that returns a slew 
of results and then the script continues to do some other work, you can clear up the 
memory used to store the initial query by using mysql_free_resul t( ). 

This is a good point to note PHP's impressive memory-handling capabilities. 
When a connection is closed (either implicitly at the end of the script or by using 
mysql_close( )) PHP clears up all of the memory used by the query. When you are 
writing your applications this is one less thing you will need to worry about. 

MYSQL_CLOSE() This closes the link to MySQL. 

int mysql_close ([int 1 i nk_i denti f i er] ) 

You don't really need to use this function, as links opened with mysqi_connect 
are closed automatically at the end of a script's execution, and it has no effect on 

mysql_pconnect( ). 

In the Content Management application we use this function because within the 
course of a script we add a user to the MySQL Grant tables (see Appendix D) and 
then reconnect to the database as that user. In a case I ike this we must first close the 
connection. 

MYSQL_DATA_SEEK() This function repositions the cursor to the row number you 
specify; the first row is 0. 

int mysql_data_seek (int resul t_identi f i er , int row_number) 

This function could be useful if for some reason you need to rewind the array 
and go through the result set a second time. If you find yourself using it frequently, 
however, it might be worth rethinking the logic in your pages. In most cases you 
should be able to get all the information you need from your array in a single pass. 

MYSQL_CREATE_DB() This function can only appear after a connection is made to 
MySQL with mysql_connect( ) or mysql_pconnect( ). The user connected to MySQL 




Chapter 6: PHP's Built-in Functions 123 

must have rights necessary to create tables. However, you could just as easily use 

mysql_query( "create database db_name"); 

int mysql_create_db (string database name [, int 1 i n k_ identifier]) 



This function and the MySQL functions that follow are fine, but we find it 
easier to use normal SQL statements (create, alter, drop, etc.) and send them 
to MySQL through the mysql_query ( ) function. They work just as well 
and can be used within scripts and in the command-line client. In the end, 
there are fewer functions that you need to remember. 



MYSQL_DROP_DB() This function removes a database from MySQL, which is 
something you probably don't want to be doing from your scripts too often. 

int mysql_drop_db (string database_name [, int 1 i nk_identi f ier] ) 

MYSQL_LIST_DBS () This function lists databases available on a MySQL server. 

int mysql_l i st_dbs ([int 1 i nk_i denti f i er] ) 

You will have to use the mysqi_i ist_tabies( ) function to retrieve the exact 
tables. Since MySQL responds just fine to the following, it's best to avoid this func- 
tion and use the mysql_fetch_array( ). 

mysql_connect( "1 ocal host" , "username", "password"); 
mysql_query ( "show databases"); 

MYSQL_LIST_ TABLES!) If given a database name this function will return a result 
identifier to a list of tables within a database. 

int mysql_l i st_tabl es (string database [, int 1 i nk_i denti fi er] ) 

Like mysqi_query( ), this function doesn't contain the results; those must be 
fetched with the mysql_tablename() function. The following example shows how 
you might use these functions to get a listing of tables from the database named "test". 

$tabl es=mysql_l i st_tab1 es ( "test" ) ; 
for($i=0; $i <mysql_num_rows( $tabl es ) ; $i++) 
{ 

echo my sql_tablena me (Stables, $i), "<br>\n"; 



124 Part II: Working with PHP 

If you don't wish to commit these functions to memory, you could use the fol- 
lowing code to achieve the same thing. 

Iresult = mysql_query ("show tables from test") or 

di e ( mysql_error( ) ) ; 
while( $row = mysql_fetch_row(lresul t) ) 
{ 

echo $row[0], "<br>\n"; 



MYSQL_LIST_FIELDS() This function returns a result identifier, which you can 
then use to grab information about the MySQL columns. 

int mysql _1 i st_f i elds (strirg database_rame , string table_name 
[, int 1 i nk_identi f i er] ) 

You must use a result identifier from this function to get information from the 
mysql_f i el d_f 1 ags ( ), mysql_f i el d_l en( ), mysql_f iel d_name( ), and mysql_ 
field_type( ) functions. All of these functions take the same arguments: the first 
is the result identifier, the second is the numeric offset of the column, starting at 0. 
These columns return about what you'd expect, respectively name flags (such as 
NOT NULL, PRIMARY KEY), the length, the name of the field, and the type (such 
as int, text, enum, and so on). 

As an example, take the table created with the following create statement: 

create table show_stuff ( 

stuff_id int not null primary key auto_i ncrement , 
stuff_desc varchar(255) null, 
stuff_stuff text 



The following script will return about everything you 'd need to know about the 
columns. (The results of the script can be seen in Figure 6-1.) 

Iresult= mysql_l i st_f i el ds ("test", "show_stuff " ) ; 

$i = 0; 

whi 1 e( $i <mysql_num_f i elds($result)) 

{ 

echo "<b>" . mysql_f iel d_name (Iresult, $i) . "</b><br>"; 

echo mysql_f iel d_f 1 ags (Iresult, $i) . "</b><br>"; 

echo mysql_f iel d_l en (Iresult, li) . "</b><br>"; 

echo mysql_f iel d_type (Iresult, li) . "<br>"; 

I i ++ ; 



Chapter 6: PHP's Built-in Functions 



125 



•"' http://192.168.1.1/test4.php - Microsoft Internet Explorer 



File Edit View Favorites lools Help 



Mi 



Address |g] http7AI92.168.1.1/test4.php 



"31 If* Go 



«" „ ■► „ © © a 

Back Stop Refresh Home 



m m 

Search Favorites History 



Print 



Edit 



stuffid 

not_null primaiy_key auto_increment 

11 

int 

stuff_desc 

not_null 

255 

string 

stuffstuff 

blob 

65525 

blob 



J 



aastartl l«>[iil^2ll ■^j_il^]±j ^'y^M^v'l 12:50 PM 



Figure 6- 1: Results of field description script 



Note once again that you can make use of MySQL's descriptive queries to 
achieve the same results. The queries "show columns from table_name" and 
"describe table_name" get all the information you need and keep you from having 
to use these functions. 



String- handling functions 



In creating Web- based applications, string handling and manipulation is one of the 
most critical tasks of the language you work with. Text cleanup and validation is 
extremely important, and good Web middleware will make working with text rela- 
tively easy. PHP excels in this department: It contains built-in functions that cover 
most anything you'd want to do to text. 

In fact, there are far more functions than we could cover here. As of PHP 4.0.2, 
there were 70 string- handling functions listed on http://www.php.net/manual / 
ref.strings.html. In this book we can cover only a portion of these. We will 
cover all of the string-handling functions we used in the course of creating the 
applications in Sections III and IV, and we will cover some other notable functions 
that we didn't have the opportunity to use. 

STRING FUNCTIONS USED IN THIS BOOK 

I thought it would be nice to start with a function that clearly demonstrates why 
PHP is so cool. 



126 Part II: Working with PHP 

STRIP_TAGS() This function removes HTML and PHP tags. 

string strip_tags (string str [, string al 1 owabl e_tags] ) 

One of the most important things that you will need to do with every Web- based 
application you write is make sure that the users of your Web pages haven't passed 
you malicious text. As we discuss in Chapter 8, if you're not careful, you might find 
your pages filled with HTML tags (<img>, <di v>, etc.) or JavaScripts that you don't 
want. You could also find yourself in real trouble if some cracker decides to litter 
your form fields with something I i ke < s c r i p t > alert ("you stink" );</script>. 

The strip_tags() function will remove all HTML and PHP tags, except for 
those explicitly allowed in the second argument. If you wanted to allow <t>> and 
<i> tags, you might use this: 

stri p_tags ( $str , "<b><i>") 

ADDSLASHESO This function is intended to work with your database insert and 
update queries. 

string addslashes (string str) 

If you take a look at a typical insert query you can see a potential problem: 

insert into tabl e_name(char_f i el d , numeri c_f iel d) 
val ues ( ' $str ' , $num) ; 

What if the value in $str contains a contraction such as "ain't"? You could get an 
error because the apostrophe is going to confuse MySQL. You will need to escape 
all occurrences of single quotes ('), double quotes ("), and NULLs in the string. 

For example: 

$strl = "1 et ' s see" ; 

$str2 = "you know" ; 

$strl = addsl ashes( $strl ) ; 

$ result = my sql_query( "insert into show_stuff 

(stuff_desc, stuf f_stuff ) val ues( ' $strl ' , '$str2')"); 
echo mysql_af fected_rows( ) ; 

So, given this potential problem, do you need to put all of your form input infor- 
mation through addsl ashes ( )? Not necessarily. It depends on the magic_quotes_ 
gpc setting in your php.ini file. If it is set to on, which is the default, data that come 
from Get, Post, or Cookies is automatically escaped, so you don't need to worry 
about putting the information through addsl ashes( ). 



Chapter 6: PHP's Built-in Functions 



127 



Make sure to check your magi c_quotes settings in your php.ini. Note that 

if set to yes, magic_quotes_runtime will automatically add slashes to 
data returned from queries and files. See Appendix B for more discussion on 
magic_quotes settings. 





In addition to the characters listed here, there are a few other characters that 
you need to escape if you are going to put them in a MySQL database. 
You can see the full list at the following URL: http://www.mysq! .com/ 
documentati on/mysql / by chapter /ma nual_Reference. html. If you'd 
like a little PHP function that will automatically escape these characters, see the 
mysql_escape_stri ng( ) function in AppendixG. 



STRIPSLASHESO This function will remove the slashes inserted by the magic_ 
quotes_gpc oraddslashes(). 

string stripslashes (string str) 

And why might this be necessary? Say you've put some form input through 
some validation and the validation fails. You are probably going to want to echo 
the values originally entered into forms back to the user. But if you do this with the 
magic_quotes on and the user enters text into the form that needs to be escaped, 
the escaping backslash will appear to the user. That is not a good thing. See 
Appendix B for more information on magic quotes. 

STR_REPLACE() This function replaces all occurrences of the first argument and 
replaces them with the string in the second argument. 

string str_replace (string to search for, string to replace with, 
string to be affected) 

For example, if we wanted to print out the names of both authors of this book, 
the following would work: 

$str = "This book written by Brad. Brad wrote a nice book."; 
$str = str_repl ace( "Brad" , "Brad and Jay", $str); 
echo $str; 



This would print: "This book written by Brad and J ay. Brad and J ay wrote a nice 
book." 



128 Part II: Working with PHP 

SUBSTR_ RE PLACE () This function operates on the string in the first argument. 
The portion of the string to be manipulated will be identified by the numbers in the 
third and fourth arguments. 

string substr_repl ace (string string, string replacement, int start 
[, int length]) 

The third argument should be the offset of the character you wish to start with. 
The fourth, which is optional, can have an integer representing the number of char- 
acters after the third argument. If the fourth argument is a negative number, the 
portion will determined from the end of the string. 

For example: 

echo substr_repl ace( "thi s are my string", "this is", 0,8); 

will print "This is my string". And: 

echo substr_repl ace( "thi s is my string", "new string!", 11,-1); 

will print "This is my new string!" 

STRCMPO This function compares two strings. 

int strcmp (string strl, string str2) 

If the first string is greater than the second the function will return a number 
greater than 0. It will return a number less than if string two is greater than 
string one. 

STRLEN() This function returns an integer that gives the number of characters in 
a string. 

int strlen (string str) 

For example: 

echo strlen("My String"); 

will print "9". 

STRPOSO This function returns the position of the string in the second argument 
within the first argument. It will return FALSE if the string isn't found. 

int strpos (string to search, string to find [, int offset]) 



Chapter 6: PHP's Built-in Functions 129 

For example: 

$str = "Where is the first space"; 
echo strpos($str, " "); 

This will return "5". If you wanted to get the first space after the fifth character 
you could make use of the optional third argument. 

$str = "Where is the first space"; 
echo strpos($str, " ", 6); 

This would return "8". 

An interesting note: Suppose you want to test if a string contains a specific 
character, say a space. You might think the following would work since strpos( ) 
returns FALSE if the string in the second argument is not found. 

i f ( s t r p o s ( $ s t r , " " ) ) 
( 

echo "thank you for including a space"; 
} el se 
{ 

echo "include a space, please"; 



But, if you remember back to Chapter 5, the value of zero will also test as FALSE. 
So in the preceding code, if $str starts with a space, the condition will evaluate as 
FALSE. Therefore, if you want to use a test like this, you will need to alter the con- 
dition. In PHP 4 you can run a test against the constant FALSE. For example, 

$str = " Wherei sthef i rstspace" ; 
if( strposdstr, " ")===FALSE ) 
t 

echo "you have not included a space."; 
} el se 
t 

echo "thank you for the space."; 



STRRPOSO This is similar to the strpos( ) function except that it finds the final 
position of the character in the second argument. 

int strrpos (string to search, char to find) 



130 



Part II: Working with PHP 



For example: 

$str = "Where is the final space"; 
echo strrpos($str, " "); 

will return "18". 

SUBSTRO This function returns a portion of a string based on numeric offsets. 

string substr (string string, int start [, int length]) 
For example: 

echo substr("this is my string", 5); 

will return everything after the fifth character. In this case "is my string". The 
optional third argument can represent the number of characters to be returned, fol- 
lowing the third argument. For example 

echo substr("this is my string", 5,2); 

will return "is". A negative number in the fourth argument specifies a character 
from the end of the string. For example, 

echo substr("this is my string", 5,-7); 

will print "is my". 




The substr function works really well with functions like strpos( ) and 
strrpos( ). 



STRREV() This function reverses the order of a string. 

string strrev (string string) 

Use of strrev ( ) may not come up too often in your programming life. In this 
book, it comes into use in the credit-card validation algorithm. 

STRTOLOWER() This function makes an entire string lower case. 

string strtolower (string str) 



Chapter 6: PHP's Built-in Functions 131 

It's particularly useful when you're dealing with something like file names. Since 
Unix file names are case- sensitive, you may want to be sure that all files that you 
write have all lowercase letters. We also make use of this function when dealing 
with passwords. 

STRTR() This function takes the string in the first argument and translates each char- 
acter in the second argument to the corresponding character in the third argument. 

string strtr (string str, string from, string to) 

or 

string strtr (string str, associative array) 

So in the following, each a will be tuned into an i and each o will be turned into 
a u. 

strtr ( $str , "ao" , "i u" ) 

Note that it does all of the replacements at once. For example, given this code 

$ s t r = " i " ; 

echo s t r t r ( $ s t r , " i u " , " u v " ) ; 

you might think that this function would first turn "i" into "u", then "u" into "v". 
This is not the case. It will do the replacements in a batch, so the output of this 
operation is "u". 

This function can also work with only two arguments. In such cases, the second 
argument should be an associative array. Then each key will be replaced by its cor- 
responding value in the string. 

$ s t r = "this is my strings"; 

$repl ace_array = array ( "thi s"=>"these" , "i s" = >"are" ) ; 

echo strtrdstr, $repl ace_array ) ; 

This code prints "these are my strings". 

Once again, the replacements are done in a batch. If you had a third element in 
the preceding array "these" =>"those", you wouldn't have to worry about the word 
"this" being changed to "these" and then being changed to "those". 

UCFIRST() This function makes the first character in the string upper case. 

string ucfirst(string str); 

Note that it leaves everything after the fist character untouched. So if you 
wanted to make sure that the first and only the first character of a string was capi- 
talized, you would have to something like this. 



132 



Part II: Working with PHP 



$str = "this is My string"; 
echo ucf i rst(strtol ower( $str) ) ; 

This would print out "This is my string". 

UCWORDSO This function makes the first character in every word in the string 
upper case. 

string ucwords( stri ng str); 

Like ucf i rst( ), ucwords( ) does not touch characters that do not start a work. 

TRIM() This function removes any white space from the beginning and end of a 
string including return characters, linefeeds, spaces, and tabs. 

string trim (string str) 



PHP also has the 1 trim( ) function to strip white space from only the start 
of a string or chop () to remove white space from only the end of a string. 




HTMLSPECIALCHARSO This function transforms <, >, &, and " into their proper 
HTML entities: <, >, &, and ". 

string html speci al chars (string string) 

This function is useful for printing HTM L source code to the browser. 



In addition to htmlspecial chars( ), you can make use of html 
entitiesO. html entities ( ) transforms every character that has an 
HTML entity. For example the copyright symbol isturned into ©. 




GET_HTML_TRANSLATION_TABLE() This function gets a full list of characters that 
have HTML entities, or just those used by html special chars( ). You can indicate 
which you need access to by including HTML_ENTITIES or HTML_SPECIALCHARS 
within the function. For example get_html_transl ation_table(HTML_ENTITIES), 

gets a full list of characters and their entities. 



Chapter 6: PHP's Built-in Functions 



133 



Each character and its entity are available as key=>value pairs in an associative 
array. The resulting array can be of use with the strtr( ) function. 

NUMBER_FORMAT() This function formats a number to your specifications. 

string number_f ormat (float number, int decimals, [[string dec_ 
point], [string thousands_sep) ]] 

The fist argument will be the number you wish to format. The second argument 
will state the number of digits you would like after the decimal point. You can use 
this function with just these two arguments. For example, 

echo number_format( (10/3), 2 ); 

will print "3.33", and 

echo number_format( 1000 , 2) ; 

will print "1,000". Note that if there are two arguments, number_format( ) includes 
a comma as a thousands separator. 

In the third and fourth arguments you can include a decimal point separator and 
a thousands separator, respectively. For example, 

echo number_format( 10000.67, 2, "&", "R"); 
will print, 10R000&67. 




If number_format( ) doesn't cut it for you, you can make use of PHP's 
sprintf ( ),printf ( ),and scant ( ) functions. If you have a background in 
Perl or C you probably know how these functions work. They're powerful, 
complex, and take a good deal of time to get used to. If you want to familiarize 
yourself with these functions, take a look at the PHP manual (http:// 
www.php.net/manual/function.sprintf.php) and this tutorial: 
http : //wdvl . com/Author ing/Scripting/Tutorial /perl _printf 
.html. 



HELPFUL STRING FUNCTIONS NOT USED IN THIS BOOK 

J ust because we didn't use them doesn't mean you won't. And again, it's entirely 
possible that something we didn't cover will suit your needs perfectly. Please look 
over the PHP manual for a complete list. 



134 



Part II: Working with PHP 



NL2BR() This function adds an HTML break (<br>) after each newline (\n) in 
a string. 

string n!2br (string string) 

Note that the newline characters will remain after going through this function. 
For example, this code 

$str = "jay 

John 

bob 

stan" ; 

echo nl 2br( $str ) ; 

will print the following (note that this is the HTML source of the resulting page): 

jay 

<br> 

John 

<br> 

bob 

<br> 

stan 

STRTOUPPER () This function makes an entire string upper case. 

string strtoupper (string string) 

MD5() md5() is a one-way algorithm that encrypts information. 

string md5 (string str) 

This function is often used for passwords. If you were to put a password in a text 
file, it is possible that someone who had (legitimate) access to your system could 
view the passwords. However, if you pass it through md5( ), the correct password is 

unknowable. For example, md5("jay") is baba327d241746ee0829e7e88117d4d5. 

If this is what is entered in the text file, those who have rights to view the database 
will not know what the correct password is. 



A safe password will be a lot more complex than "jay'.'A cracker could (and 
will) run an entire dictionary through md5( ) to see if something allows 
entry to the system. 





Chapter 6: PHP's Built-in Functions 135 



md5( ) is one-way only.There is no way to un-encryptit.lfyou are interested 
in two-way encryption look to the mycrpt functions in the PHP manual: 

http: //www. php. net/manual /ref.mcrypt.php. 



Regular expression functions 

Regular expressions offer a method for complex pattern matching. If you're new to 
the concept of regular expressions, consider this: Given the string handling func- 
tions you have seen so far, how could you insert a newline and a break (\n<br>) 
after every 45 characters? Or how could you find out if a string contains at least 
one uppercase letter? You may be able to pull it off, but it won't be pretty. 
By the way, the following code will solve the previous two questions. 

//insert \n<br> after each 45 characters 

$new_str = ereg_repl ace( " ( . (45 } ) " , "\\l\n<br>", $str); 

//check if string contains uppercase letter 

if (ereg("[A-Z]" , $str)) 

( 

echo "yes i t does . " ; 



Statements like these may seem a bit opaque at first, but after working with 
them for a while, you will grow to love the convenience they offer. 



See Appendix F for a rundown on how regular expressions work. 




Note that regular expressions are a good deal slower than string-handling func- 
tions. So if you have, for example, a simple replace that doesn't require regular 

expressions, use str_repl ace( ) and not ereg_repl ace( ). 

REGULAR EXPRESSION FUNCTIONS USED IN THIS BOOK 

The following regular expression functions are used in the application in this book. 

EREG() Tests whether a string matches a regular expression. 

int ereg (string pattern, string string [, array regs]) 



136 Part II: Working with PHP 

You can use this function in two ways. First, you can place a regular expression 
in the first argument and search for its existence in the second argument. The func- 
tion will return TRUE or FALSE, depending on the outcome of the search. For 
example: 

if ( ereg(" A http://.*" , $str) ) 
{ 

echo "This is a URL"; 



The optional third argument is an array that is created from the regular expres- 
sion. The portions of the regular expression that will become elements in the array 
are indicated by parentheses in the regular expression. 

ereg( "(....)-(..)-(..)" , $publ i sh_date , $date_array ) ; 

This example, which was taken from the Content Manager application, creates 
an array named $date_array, wherein the first element will be the complete string 
matched by the regular expression. The next three elements in the array will be the 
portions indicated by the parentheses. So $date_array[i] will contain 4 charac- 
ters, and $date_array[2] and date_array [3] will contain 2 characters each. 

Note that arrays created by the third argument in ereg( ) will always contain 11 
elements. Even if you have more than ten substrings within parentheses, only the first 
10 will be put in the array. If you have fewer than 10 substrings, the array will still 
contain 11 elements. If you want to test whether anything exists in the array element, 
you have to test against an empty string (""). An isset( ) will always test TRUE. 

So, after running this code: 

$publ ish_date = "2000-10-02"; 

ereg( "(....)-(..)-(..)" , $publ i sh_date , $date_array ) ; 

$date_array would contain the following: 

[0] => 2000-10-02 

[1] => 2000 

[2] => 10 

[3] => 02 

[4] => 

[5] => 

[6] => 

[7] => 

[8] => 

[9] => 

Note that ereg( ) performs a case- sensitive match. 



Chapter 6: PHP's Built-in Functions 137 

EREGIO This function is a case-insensitive version of ereg( ). 

int eregi (string pattern, string string [, array regs]) 

EREGREPLACEO You can use this function for string replacement based on com- 
plex string patterns. 

string ereg_replace (string pattern, string replacement, string 
string) 

For example, if you wanted to delete the query string from a URL, you could use this: 

$url = "http: //www.phpmysql book.com/index.php?var=hello"; 
$parsed_url = ereg_repl ace( "\? .*\$" , "",$url); 
echo $parsed_url ; 

This would print http://www.phpmysqlbook.com/index.php. This regular 
expression matches a question mark, and all characters that occur after it until the 
end of the line. The question mark must be escaped with a backslash because it has 
a specific meaning to the regular expression. Following the question mark we 
match any number of characters until the dollar sign, which is the endline charac- 
ter. It needs to be escaped with a backslash because without the backslash, PHP will 
think the character represents a variable. 

But often you will need a bit more functionality than this. What if you want to 
preserve the string you are searching for in the replacement string? Or what if your 
search contains distinct portions offset by sets of parentheses? Here's a simple 
example. We want to replace the current querystring by placing an additional 
name=val lie pair between the two name=val ue pairs currently in the string. That is, 
we want to put "newvar=here" after "var=hello" and before "var2=yup". 

$url= "http: //www.phpmysql book.com/index.php?var=hello&var2=yup"; 
$parsed_url = ereg_repl ace( " ( \? .*&) " , "\\lnewvar=here&" , $url ) ; 
echo $parsed_url ; 

This creates the following string: 

http: //www.phpmysql book.com/index.php?var=hello&newvar=here&var2=yup 

Here the single set of parentheses indicates portion 1. Then, by using the nota- 
tion \\l, we can include that portion in the newly created string. If more than one 
portion is indicated by additional parentheses, you can echo the others back into 
the result by noting which portion you need. 

$ u r 1 = "this is a test " ; 

$parsed_url = ereg_repl ace( " ( thi s .*a ) .*(test) " , "\\1 regular 



138 Part II: Working with PHP 

expressi on \\2" , $url ) ; 
echo $parsed_url ; 

The result of these commands is: "this is a regular expression test". 

The regular expression matches everything between "this" and "test". We use 
parentheses to indicate a substring that starts with "this" and moves to the letter "a". 
The next .* portion matches any number of characters. Finally, "test" is another sub- 
string. These substrings are echoed back in the second argument, with \\1 echoing 
the first substring and \\2 echoing the second substring. 

The regular expression match is case- sensitive. 

EREGI REPLACE!) This is the same as ereg_replace( ), except that the match is 
case-insensitive. 

REGULAR EXPRESSION FUNCTIONS NOT USED IN THIS BOOK 

The following regular expression functions, while not used in the examples in this 
book, are still useful to know. 

SQL_REGCASE() This nifty little function will alter strings so that you can use 
them in case- in sensitive regular expressions. 

string sql_regcase (string string) 

This might be of use if you are doing a regular expression search in a database 
server that doesn't support case-insensitive regular expressions. It will save you 
from having to type in every character in a string as both an uppercase and lower- 
case letter. For example: 

echo sql_regcase("this string"); 

produces: 

[Tt][Hh][Ii][Ss] [Ss][Tt][Rr][Ii][Nn][Gg] 

PERL- COMPATIBLE REGULAR EXPRESSIONS (PCRE) 

For years, the Perl programmers of the world have had regular expressions unlike 
any others. If you have some experience with Perl, it's likely that you've come to 
love the additional power these regular expressions give you. If you don't come 
from a Perl background, you might enjoy learning a bit about the features. 

PCREsare, however, a fairly large topic, one that Appendix F explains only briefly. 
However, if you're looking to get a good jump on learning about Perl's regular 
expressions and how they can work for you, the information at the following URL is 
a good read: http://www.perl.com/pub/doc/manual/html/pod/perlre.html. 
There is also a decent description of Perl regular expressions in the PHP4 manual: 
http: //www.php. net/manual /pcre. pattern. syntax. html. 



Chapter 6: PHP's Built-in Functions 139 

The major reason for using PCRE functions is that they give you choice between 
"greedy" and "non-greedy" matching. For a quick example, take the following string. 

$str = "I want to match to here. But end up matching to here" 

Using eneg( ) or eneg_nepl ace( ) there is no way to match from "I" to the first 
occurrence of "here". The following will not work as you might expect: 

$str = "I want to match to here. But end up matching to here"; 
$new_str = ereg_repl ace( " I .*here" , "Where", $str); 
echo $new_str; 

This will print "Where" and nothing else. The entire string will be replaced. Using 
ereg_repiace( ) there is no way to indicate that you want to match to the first 
occurrence of "here". However, using preg_repl ace( ), you could do the following: 

$str = "I want to match to here. But end up matching to here"; 
$new_str = preg_repl ace( "/I .*?here/" , "Where", $str); 
echo $new_str; 

In this instance, .*? means "match all characters until the first occurrence". 

PCRE FUNCTIONS USED IN THIS BOOK 

PREG_MATCH() This is similar to the ereg( ) function in that you can assign the 
optional third argument an array of matched subpatterns, if any are found in the 
regular expression. preg_match returns the number of pattern matches found or 
False, if none are found. 

int preg_match (string pattern, string subject [, array matches]) 

PREG_REPLACE() This makes replacements based on Perl regular expressions. 

mixed preg_replace (mixed pattern, mixed replacement, mixed subject 
[ , int limit]) 

This is similar to ereg_repl ace( ), though the pattern here must be a Perl regu- 
lar expression. It can also make use of Wdigit to echo the matched substring into the 
result. The optional fourth argument will limit the number of replaces that preg_ 
replace makes. 

preg_replace("/[<br> \s]*$/i","",$body); 



140 Part II: Working with PHP 

This example, taken from the content management system application, will 
remove all occurrences where breaks (<br>), non-breaking spaces ($nbsp;), or 
white space (spaces, tabs, new lines) appear consecutively. This replacement is not 
case- sensitive (the "i" flag determines that) to ensure that both <br> and <br> are 
matched. 

The brackets indicate that anything within the brackets will start the match. The 
asterisk indicates that if the character following the first match is also one of the 
characters within the brackets, the pattern is matched and a replace should occur. 

PCRE FUNCTIONS NOT USED IN THIS BOOK 

There are a few PCRE functions that we did not use to create these applications. They 
are: preg_match_all, preg_quote( ), and preg_grep( ). Seethe online manual for 
their usage. Note that we will discuss preg_spi it( )in the next section. 



Type- conversion functions 



This is a category of my own making. In the manual, these functions will fall under 
other headings. However, we feel that the specialized nature of these functions 
demands a unique category. 

Chapter 4 discusses PHP variables in detail, including PHP's flexible variable 
typing. If you recall, if you need to evaluate a string as if it were an integer, you 
can make use of the intval ( ) function. See Chapter 4 for similar variable conver- 
sion functions. 

But at times the variable conversion will be a bit more extreme, turning strings 
into arrays and arrays into strings. Why, you ask, might you want to do this? 
Consider a string like the following: 

24,16,9,54,21,88,17 

So you have this string of integers, maybe retrieved from a text file. How would 
you go about sorting it in ascending order? If you have to deal with it as a string 
the code is going to get very nasty. However, if you can make use of myriad array 
functions, life gets quite a bit easier. You could simply use the sort( ) function. 
Take a look: 

$str = "24,16,9,54,21,88,17"; 

//turn $str into an array 

larray = expl ode( " , " , $str); 

//sort the array in ascending order 

sort($array, SORT_NUMERIC) ; 

//turn the array back into a string and print 

$new_str = impl ode( " , " , larray); 

echo $new_str; 



Chapter 6: PHP's Built-in Functions 141 

This will print: 

9,16,17,21,24,54,88 

More on the sort( ) function a bit later. 

TYPE CONVERSION FUNCTIONS USED IN THIS BOOK 

The following type conversion functions are used in the examples in this book. 

EXPLODEO This function transforms a string into an array. 

array explode (string separator, string string [, int limit]) 

The second argument is the string you wish to break into an array. The first is 
the character or characters that separate the different elements. In the example 
immediately above, the string is separated on a comma. 

The third argument limits the number of elements in the resulting array. If you 
were to use the following code 

$str = "24,16,9,54,21,88,17"; 
//turn $str into an array 
$my_array = expl ode( " , " , $str, 3); 

$my_array would have three elements: $my_array[0] => 24 $my_array[l] => 

16 $my_array[2] => 9 , 54, 21 ,88 , 17. You can see that the last element contains 
what's left of the original string. If you wanted to sort only the first three elements 
in a string and discard the rest you might do this: 

$str = "24,16,9,54,21,88,17"; 
//turn $str into an array 
$array = explode(",", $str, 4); 
unset( $array[3] ) ; 
sort($array, SORT_NUMERIC) ; 
echo impl ode( " , " , $array); 

If the string separator does not exist, the entire string will be placed in array ele- 
ment zero. If the string does not exist, an empty string will be placed in the first 
element. 

IMPLODEO As you might expect, impl ode () is the opposite of exploded: it 
turns an array into a string. 

string implode (string glue, array pieces) 



142 



Part II: Working with PHP 



The first argument is the string that will separate the string elements. The second 
is the array to be separated. 

A good example of when you might use implode ( ) is in a page that runs an SQL 
delete command. Say that in a page you have presented a series of checkboxes to 
indicate the rows you wish to delete from the database. You are probably going 
to want to pass the elements you wish to delete within an array. In the page that 
does the deletes, you could then run a script like this: 

//say $del eted_comes from an HTML page and 

//contains (1,3,7) 

if( i s_array( $del ete_i terns ) ) 

{ 

$str = implode("," , $del ete_i terns ) ; 

$query = "delete from table where item_id in ($str)"; 

mysql_query ( $ query ) ; 
} 

SPLIT() The spl it function does the same thing as explode, but it allows you to 
specify a regular expression as the separation string. 

array split (string pattern, string string [, int limit]) 

This could come into play if you want to separate a string based on more than 
one element. Say you had a string you needed as an array, the elements of which 
could be separated by either a new line or a tab. The following would do the trick: 

//note there is a tab between 524 and 879 

//and a tab between 879 and 321 

litems = "524 879 321 

444 

221"; 

larray = spl i t( " [\n\t] " , "litems"); 




spl i t( ) is more flexible than expl ode( ), but it's also slower. 



PREG_SPLIT() This works like spl i to, only it uses a Perl regular expression as 
the pattern. 



array preg_split (string pattern, string subject [, int limit [, int 
flags]]) 



Chapter 6: PHP's Built-in Functions 143 

Note that if the flag is PREG_SPLIT_NO_EMPTY, empty items will not be placed 
in the array. 
Again, if expiode( ) can do it, make sure to use it. 

TYPE CONVERSION FUNCTIONS NOT USED IN THIS BOOK 

In addition to the functions in the previous section, you can make use of spi iti ( ), 
which uses a case-insensitive pattern match. 



Array functions 




I am a big fan of the array functions available in PHP 4. J ust about anything you'd 
I ike to do to an array you can do with a built-in function. The developers of PHP have 
done a good job of making sure you have to loop though arrays very infrequently. 

In the PHP 4.0.2 manual there are exactly 47 listed array functions. It's likely 
that by the time you read this chapter, there will be several more. So make sure you 
scan the manual to see the full range of available array functions. 



See Chapter 5 for a discussion of how to create, add to, and walk through 
an array. 



ARRAY FUNCTIONS USED IN THIS BOOK 

When you're dealing with database applications, much of your logic should come 
within your SQL statements. Thus, in the applications presented in this book fairly 
few array functions were necessary. Here's a rundown of the ones we used. 

ARRAY_FLIP() This function, which is useful with associative arrays, exchanges the 
keys and values. That is, the keys become the values and the values become the keys. 

array array_flip (array trans) 

This comes up once in the course of the book, in the following code: 

$ trans = array_fl i p(get_html_transl ation_tabl e( HTML_ENTITI ES) ) ; 
$title = strtr($title, $trans); 

Before the array_flip() function, the array will return many elements. Here 
area couple of examples: 

[(c)] => &copy 
[(r)] => &reg 



144 Part II: Working with PHP 

Once the array is flipped, these entries will look like this: 

[$copy] => (c) 
[&reg] => (r) 

Then strtr( ) replaces each value to its key. So in the end this code will make 
sure that any character that needs to be represented by an HTM L entity will be. 

Note that if an array has two identical values before being flipped, only one can 
survive in the flipped array. You can't have two array elements with same key. If 
there is a conflict the element in the right-most position will be maintained. 

ARRAY_MERGE() As you can probably guess, this function merges, or concate- 
nates, two or more arrays. 

array array_merge (array arrayl, array array2 [, array ...]) 

If any of the arrays contain the same associative keys, the elements in the right- 
most array will be preserved. 

ARRAY_ SPLICE () This function takes the array indicated in the first argument and 
removes all elements following the offset specified in the second argument. It can 
then insert additional elements. 

array array_splice (array input, int offset [, int length [, array 
repl acement] ] ) 

If the offset is a positive number, the elements will be counted from the left; if the 
offset is a negative number, all items to the left of the indicated number will be 
deleted. The optional third argument can indicate how many elements after the offset 
you wish to delete. For example: 

$ kn i cks_array = array ("Chi Ids", "Sprewell", "Ewing", 

" J o h n s o n " , " H o u s t o n " ) ; 

array_spl i ce( $kni cks_array , 2,1); 

will remove elements starting at offset 2 and remove only one element. So "Ewing" 
will be deleted from this array. array_spi ice( ) also gives you the ability replace the 
deleted portion with another array. So, to account for trades, you can do this. 

$ kn i cks_array = array ("Chi Ids", "Sprewell", "Ewing", 

" J o h n s o n " , " H o u s t o n " ) ; 

$new_knicks = array ( " Longl ey" , "Ri ce" ) ; 

array_spl i ce( $ kn i cks_array , 2,1, $new_kni cks ) ; 



Chapter 6: PHP's Built-in Functions 145 

Following this code, $knicks_array would contain six elements: Childs, Sprewell, 
Longley, Rice, Johnson, Houston. 

Note that the value returned by this function is an array of the deleted items. In 
the code that follows, $traded_knicks will be an array with one element, "Ewing". 

$traded_kni cks = array_spl i ce( $kni cks_array , 2,1); 

COUNTO This returns the number of elements in an array, and is frequently used 
with loops. 

int count (mixed var) 

For example: 

$array = array ( 1 , 2 ,3 ,4 , 5) ; 
for($i=0; $i<count($array) ; $i++) 
t 

echo $array[$i] . "<br>\n"; 



Note that sizeof ( ) is a synonym for count ( ). 

ARRAY FUNCTIONS NOT USED IN THIS BOOK 

Again, there are many great array functions in PHP 4. Here are some of the highlights 
(from my point of view, anyway). 

ARRAY_COUNT_VALUES() This nifty function will return an associative array, the 
keys of which will be all of the unique values within the array. 

array array_count_val ues (array input) 

The values of the resulting array will be an integer representing the number of 
times the value appears within the array. 

$array = array ( "yes" , "no" , "no" , "yes" , "why" ) ; 
$result = array_count_val ues ( $array ) ; 

After this $resul t will contain: 

[yes] =>, 2, [no] => 2, [why] => 1 

ARRAY_DIFF() If given two arrays, this function will return all of the elements 
that are in the first array, but not in the second array. 

array array_diff (array arrayl, array array2 [, array ...]) 



146 Part II: Working with PHP 

For example: 

Iknicks = array( "sprewel 1 " , "houston", "Ewing", "childs"); 

$all_stars = array ( "mourni ng" , "houston", "carter", "davis", 

"miller"); 

$non_kni ck_al 1 stars = array_di ff ( $al l_stars , $knicks); 

Note that in the returned array, the elements maintain the keys they had in the 
array from which they were taken. So after running this code, lnon_knicks_array 
will contain the following: 

[0] => mourning, [2] => carter, [3] => davis, [4] => miller 

Additional arrays can be added to the function. For example, 

Iknicks = array( "sprewel 1 " , "houston", "Ewing", "childs"); 

$all_stars = array ( "mourni ng" , "houston", "carter", "davis", 

"miller"); 

$non_kni ck_al 1 stars = array_di ff ( $al l_stars , $knicks, 

array ( "carter" ) ) ; 

Given this, "carter" will also be removed from the returned array. 

ARRAY_ INTERSECT!) This returns the array elements that two (or more) arrays 
have in common. 

array array_i ntersect (array arrayl, array array2 [, array ...]) 

ARRAY_POP() The array_pop( ) function returns the last element in an array, and 
removes that element from the original array. 

mixed array_pop (array array) 

For exampl e , larray = array ( 1 , 2 ,3 ,4 , 5) ; 

$ i n t = array_pop( $array ) ; 

After this runs, $ar ray will contain (1,2,3,4), and $ i n t will contain 5. 

ARRAY_PUSH() This function will add elements to the array indicated in the first 
argument. 

array_push (array array, mixed var [, mixed ...]) 

The additional arguments will be values you wish to tack onto the array. 

larray = array (1,2,3); 
array_push( larray, 4, 5, 6) ; 



Chapter 6: PHP's Built-in Functions 147 

The resulting array will contain 1,2,3,4,5,6. 

ARRAY_RAND() This function will pick one or more random elements from an array. 

mixed array_rand (array input [, int num_req]) 

Note that it does not pick the value; rather it picks the key of the chosen ele- 
ments. For instance, given the following, 

srand ((double) microtimeO * 1000000); 
Snames = array("jay", "brad", "John", "Jeff"); 
$rand_keys = array_rand ($names, 2); 

$rand_keys will contain an array with two numbers. To get the values from the 
$names array, you will first need to get to the key value extracted by array_rand( ), 
and so you will need to use something like this: 

echo $names[$rand_keys[0] ] ; 




Seed the random number generator only once per script. 



SHUFFLEO This function randomizes the elements in an array. 

void shuffle (array array) 

You will need to seed the random number generator before using it. For instance: 

srand ((double) microtimeO * 1000000) 
shuff 1 e ( $array ) ; 

IN_ARRAY() This very convenient function will search all of the values of an 
array, and return TRUE if the value in the first argument is found in the array in the 
second argument. 

bool in_array (mixed needle, array haystack) 

You might be wondering if there is an i n_keys ( ) function. Actually, there is no 
need for such a function, because the following will serve the same purpose. 

i f ( i sset( $array [" key" ] ) ) 




148 Part II: Working with PHP 

SORT() If there is no second argument, this function will sort an array in ascend- 
ing or alphabetical order. 

void sort (array array [, int sort_fl ags] ) 
The flags can be: 

♦ SORT_NUM ERIC— compare items numerically 

♦ SORT_STRING —compare items as strings 

If the array you wish to sort has only numbers, PHP will sort the array numeri- 
cally; if the array contains only strings, it will be sorted alphabetically. If the array 
has a mix of strings and numbers, it defaults to sorting by a string. 



PHP4 offers many other ways to sort arrays. Please look at the manual entries 

forarsort(),ksort(),rsort( ),and usort( ). 



Print functions 

There are a few functions that you can use to print information to the screen. Only 
two pop up in this book, but you should be aware of all the functions listed in this 
section. 

PRINT FUNCTIONS USED IN THIS BOOK 

In this case the word "functions" may be something of a misnomer. For instance, 
print( ) is probably better described as a language construct. In any case, you will 
use all of these very much like you will use functions; thus, they are included here. 

PRINT As you would expect, this prints what you specify. 

print 

ECHO This also isn't a function, but a language construct. We use it constantly 
throughout this book, so at this point you probably know what it does. 

echo 

Keep in mind that you can mix variables and code within double quotes. 

$ v a r = "this string"; 
echo "Please print $var"; 



Chapter 6: PHP's Built-in Functions 



149 



However, within single quotes the string will be treated literally: 

$ v a r = "this string"; 
echo 'Please print $var'; 

The preceding code will print "Please print $var". This concept is discussed in 
greater detail in Chapter 4. 




print versus echo. Which should you use?This is very much a matter of per- 
sonal preference: use whichever you think looks better on the page. There's 
only one major difference between the two, and this may influence your 
decision. echo can take multiple arguments.That is, with echo, different por- 
tions can be separated by commas.This will work: 
echo "this is part 1 " , "this is part 2 " ; 
Butthiswill not: 
print "this is part 1", "this is part 2"; 



PRINT FUNCTIONS NOT USED IN THIS BOOK 

They didn't come up here, but these are really important to know about. 

PRINTF() This function outputs a string using the format specified in the description 

of the sprintf ( ) function. 

PRINT_R() This function is great for putting to productive use the time you'd other- 
wise spend pulling your hair out. It prints the entire contents of any variable— most 
notably arrays and objects— to the screen. 

void print_r (mixed expression) 

We use it frequently when we're not getting the results we expect from arrays or 
objects. 




Do not do print_r($GLOBALS).You will create an infinitely recursive loop. 



150 



Part II: Working with PHP 



VAR_DUMP() This function behaves like print_r, but gives you a bit more infor- 
mation. 

void var_dump (mixed expression) 

In addition to printing out the contents of a variable, it includes the data type- 
including the data type for each element in an array or object. The same caution 
given for print_r applies to var_dump( ). 

Date/time functions 

In point of fact, dealing with PHP and MySQL as a team, you will have to get to 
know two sets of date/time functions— and they are quite different. This isn't the 
time or place to go into MySQL's functions (see Appendix I for that). PHP's 
time/date functions are very well designed. 

DATE/TIME FUNCTIONS USED IN THE BOOK 

The following are some date/time functions used in the applications in this book. 

DATE() You can use this function and the indicators outlined next to return the 
date and time. 

string date (string format [, int timestamp]) 

If you include a second argument, that value will be formatted as you prescribe. 
Otherwise, the current time and date will be used. 




The time and date the functions return are based on the time on the server. 
You will need to make use of JavaScript to get an idea of the time on the 
client's computer. 



Often the second argument will be the result of the mkti me ( ) function, 
which we discuss next. 




You can format the date using any of the indicators in Table 6-1. 



Chapter 6: PHP's Built-in Functions 



151 



Table 6-1 INDICATORS FOR THE DATED FUNCTION 



Indicator 

a 
A 
B 
d 
D 
F 

g 

G 

h 

H 

i 

I [capital i] 

J 

I (lowercase I 

L 

m 

M 

n 

s 

S 

t 

T 

U 

w 



Meaning 

am or pm 

AM orPM 

Swatch Internet time 

Day of the month, 2 digits with leading zeros; 01 to 31 

Day of the week, textual, 3 letters; e.g. Fri 

M onth, textual, long; e.g. J anuary 

Hour, 12- hour format without leading zeros; 1 to 12 

Hour, 24-hour format without leading zeros; to 23 

Hour, 12- hour format; 01 to 12 

Hour, 24- hour format; 00 to 23 

Minutes; 00 to 59 

1 if Daylight Savings Time, otherwise 

Day of the month without leading zeros; 1 to 31 

Day of the week, textual, long; e.g. Friday 

Boolean for whether it is a leap year; or 1 

Month; 01 to 12 

Month, textual, 3 letters; e.g. J an 

Month without leading zeros; 1 to 12 

Seconds; 00 to 59 

English ordinal suffix, textual, two characters; e.g. th, nd 

Number of days in the given month; 28 to 31 

Time- zone setting of this machine; e.g. M DT 

Seconds since the epoch (midnight, January 1, 1970) 

Day of the week, numeric, (Sunday) to 6 (Saturday) 



Continued 



152 Part II: Working with PHP 



Table 6-1 INDICATORS FOR THE DATE() FUNCTION (Continued) 



Indicator 


Meaning 


Y 


Year, four digits; e.g. 1999 


y 


Year, two digits; e.g. 99 


z 


Day of the year; to 365 


Z 


Time- zone offset in seconds (-43200 to 43200) 



For example, if you want to print the date in the format, "September 14, 2000 
7:21 pm," this would do the trick: 

echo date("F d, Y g:i a"); 

In case you're wondering about the significance of the above date: it was the 
exact time we wrote this portion of this chapter. 

MKTIMEO This function is most useful for calculating valid dates. 

int mktime (int hour, int minute, int second, int month, int day, 
int year [, int is_dst]) 

For example, say that you have a form that collects a date, maybe the current 
month, day, and year. You want to calculate and set a due date exactly 30 days 
from the date submitted. 

$year = 2000; 

Imonth = 5; 

$day = 24; 

echo dateC'l F d, Y", mktimeCO ,0 , , $month , $day+30 , $year) ); 

This will output 30 days from May 24, 2000 and will print out "Friday J une 23, 
2000." 

Keep in mind that this function allows you to add or subtract dates without wor- 
rying that PHP will return a fictitious result. In the previous example, you could 
subtract 6 from the month value of 5, and PHP would return a meaningful date. 
You can add or subtract any number of years, months, or days without worrying 
that PHP will return a bad result. For instance, this is a perfectly acceptable way to 
get date information on the last day of 1999. 



Chapter 6: PHP's Built-in Functions 153 

$year = 2000; 

$month = 1; 

$day = 1 ; 

echo date("l F d, Y", mktime(0 ,0 , , $month , $day-l , $year) ); 

This code will let you know that December 31, 1999 was a Friday. 

Notice that the preceding code first calculates the timestamp of the date indicated 
by mktime( ) and then prints that out using the date function. 

If you exclude arguments from the right, those parameters will be retrieved from 
the current timestamp. So, to print what the date and time will be in five hours, this 
will do the trick: 

echo date("l F d, Y g:i a", mktime( date(H)+5) ); 

Note the nesting of functions here. Starting at the innermost function, first 
date(H) returns the current hour, in 24-hour format. Then five is added to that, 
and the timestamp is calculated for five hours in the future. The timestamp is then 
formatted using the string indicated. 

TIME() This function returns the current time measured in the number of seconds 
since the Unix Epoch (J anuary 1 1970 00:00:00 GMT). 

int time( void) ; 

MICROTIMEO This function returns the string "msec sec" where sec is the current 
time measured in the number of seconds since the Unix Epoch (0:00:00 J anuary 1, 
1970 GMT), and msec is the microseconds part. 

string mi crotime( void) ; 

This function is only available on operating systems that support the gettime 
of day ( ) system call. 

The returned string will look something like 0.12082400 969034581. With this 
function you can be reasonably sure that it will never return the same number 
twice. It is often used to seed the random number generator. 

DATE/TIME FUNCTIONS NOT USED IN THE BOOK 

There a few other time/date functions that you might find useful. They include sev- 
eral for printing the current date and time. If you need to know about something 
specific that isn't discussed here, take a look at the manual: http://www.php.net/ 
manual/ref.datetime.html. 



154 Part II: Working with PHP 

Filesystem functions 

PHP has a whole range of functions that enable you to manipulate files and direc- 
tories on the host computer. In the course of creating applications for this book, 
there was only one occasion when files needed to be written to or taken from the 
filesystem: in the Catalog (and Shopping Cart) when we needed to store images that 
have been uploaded. 

But if you work with PHP frequently there's little doubt that you will need to 
become familiar with these functions. By way of introduction, we will say that the 
directory and filesystem functions in PHP are simply terrific. The programmers 
have really done a great job of making working with files, either on the local sys- 
tem or elsewhere on the Internet, a piece of cake. J ust to give a quick example, it 
took about two minutes to write this script, which will grab a stock quote from a 
site we will not specify for legal reasons. 

$farray = f i 1 e( "http: //domai n . com/stockquote?symbol s=0RCL" , "r"); 

foreach ($farray as lvalue) 

{ 

if( eregC'last: .*$" , lvalue) ) 
{ 

lvalue = stri p_tags( Ival ue) ; 
break; 



This brief script slurps up an entire page and assigns each line to an element in 
the $farray. We then loop through the array looking for the string "last". On the site 
we played with, the word "last" indicates the most recent quote. All we had to do 
was strip the HTML tags, and we had all the information we needed. If we wanted 
to, we could have done some more string processing to format the information in a 
way we liked. 

FILESYSTEM FUNCTIONS USED IN THIS BOOK 

If you would like to see these in use, check out Chapters 10 and 14. 

FOPEN() This opens a file pointer to the indicated file or URL in the first argument. 
(The pointer is very much like the result identifier returned by mysql_connect( ).) 

int fopen (string filename, string mode [, int use_i ncl ude_path] ) 

The mode will determine what you can do with the file. Table 6-2 shows the 
available modes. 



Chapter 6: PHP's Built-in Functions 155 



Table 6-2 MODES FOR THE FOPENO FUNCTION 



Mode Meaning 

r Open for reading only; place the file pointer at the beginning of the file. 

r+ Open for reading and writing; place the file pointer at the beginning 

of the file. 

w Open for writing only; place the file pointer at the beginning of the file and 

truncate the file to zero length. If the file does not exist, attempt to create it. 

w+ Open for reading and writing; place the file pointer at the beginning of the 

file and truncate the file to zero length. If the file does not exist, attempt 
to create it. 

a Open for writing only; place the file pointer at the end of the file. If the file 

does not exist, attempt to create it. 

a+ Open for reading and writing; place the file pointer at the end of the file. 

If the file does not exist, attempt to create it. 



Note that this function returns a resource identifier. If you wish to read from or 
write to a file you will need to do something like this: 

//open a file and read contents into a variable 
$filename="test99.txt" ; 
$fp = f open( $f i 1 ename , "r+") or 
d i e ( "could not open file"); 
$contents = fread ($fp, f i 1 esi ze( $fi 1 ename) ) ; 
//replace all occurrences of Jayso 

$new_contents = str_repl ace( " Jayson" , "Jay", $contents); 
//write out new file contents, 
rewi nd( $fp) ; 

fwrite($fp, $new_contents ) ; 
//ftruncate assures there wont be extra 
//characters if the resulting file is shorter 
//than the original. 
ftruncate($fp,ftell ($fp)); 
fcl ose($fp) ; 

FCLOSEO This function closes the pointer to a file. 

int fclose (int fp) 



156 Part II: Working with PHP 

Make sure to use it when you are done with a file. If you don't PHP will do it for 

you, just like mysql_close( ). 

FEOF() This function tests whether a file pointer has reached the end of a file. 

int feof (int fp) 

See the fgets( ) function for an example of feof ( ). 

FGETSO This function returns a single line from the file indicated by the file 
pointer (usually taken from fopen). 

string fgets (int fp, int length) 

If you are working with a large file, it's easier on the system to load files into 
memory one line at a time, rather than in one big chunk as is done with f read( ). 

This function will read a line up until a newline character. Optionally, you can 
specify the maximum number of bytes to read within a line in the second argu- 
ment. The number 2048 is traditionally used in the second argument because on 
many filesystems that was the maximum line length. These days, you're safe using 
something larger. You shouldn't use this function with binary files. 

$fp = fopen( "/path/to/f i 1 e" , "r" ) ; 

while ($fp && !feof($fp)) 

{ 

print fgets($fp,2048) ; 
} 
fclose($fp) ; 

FILE() This function reads a file line by line, each line becoming an element in 
an array. 

array file (string filename [, int use_i ncl ude_path] ) 

UMASKO This function sets the umask value (see your Unix man page if you 
don't know what this is). 

int umask (int mask) 

umask ( ) sets PHP's umask to mask & 0777 and returns the old umask. 

COPY() This function makes a copy of the file in argument one and copies it to 
the location in argument two. 

int copy (string source, string dest) 



Chapter 6: PHP's Built-in Functions 157 

If the copy works, the function returns True. If not, it returns False. This function 
is used in Chapter 10. 

TMPNAME() This creates a unique file name in the directory indicated in the first 
argument. 

string tempname (string dir, string prefix) 

The string prefix in argument two will be placed before each file name. This 
could help you keep track of what files belong to what scripts. 



On Windows, the behavior of this function can be a bit unpredictable. 




DIRNAMEO This function will return the directory name of the supplied string. 

string dirname (string path) 

For example: 

echo dirname( "/www/htdocs/teswri te . txt" ) ; 

will return /www/htdocs. 

FILESYSTEM FUNCTIONS NOT USED IN THIS BOOK 

This is an important topic, and one you should spend some time learning. Most of 
the more popular files system commands are available through PHP, and there are 
many commands for opening, reading, writing and displaying files. But, as this 
book deals with a relational database for data storage, we will not cover them here. 

Random number generator functions 

Every now and then you will need to pick something at random. It may be an indi- 
vidual element, or it may have to do with randomizing an array with shuffle( ) or 
getting a random element from an array with array_rand( ). In any case you will 
need to make use of PHP's random number generator functions. 

Note that the random number generator needs to be seeded before use. That is, it 
has to be given a number that is reasonably unique to begin with. For this, as you 
will see, the microtime( ) function will be of great use. 

Keep in mind that there are really two sets of random number generators, There 
are the standard rand( ) and srand( ), which you need in order to seed the genera- 
tor for shuffl e( ) and array_rand( ). However, if you just want to get a random 



158 Part II: Working with PHP 

number and not use it with any other functions, use the mt functions described 
below —they're faster and more random 

RANDOM NUMBER GENERATOR FUNCTIONS USED IN THIS BOOK 

Now we will examine some important random number generator functions not 
used in the applications in this book. 

MT_SRAND() This function seeds your random number generator. 

void mt_srand (int seed) 

Use the following line and you can be sure your numbers are plenty random: 

mt_srand ((double) microtimeO * 1000000); 

Seed the random number generator only once per script. 

MT_RAND() This function returns a random number. You can specify a minimum 
value and/or a maximum value. 

int mt_rand ([int min [, int max]]) 

So to get a random number between 1 and 100, do the following: 

mt_srand((double)microtime( ) * 1000000); 
$number = mt_rand( 1 , 100) ; 
echo $number; 

cURL functions 

These are explained in detail in Chapter 14. cURL is a library that allows for commu- 
nication between servers using a variety of protocols. For the sake of the applications 
in this book, cURL was most useful for its ability to communicate with HTTPS. This is 
a secure protocol used in the shopping cart to do credit card transactions. 

Session functions 

These are explained in detail in Chapter 14. Sessions are means of maintaining 
state between pages. Remember that HTTP, the language of the Web, does not allow 
servers to remember much of anything between requests for pages from a specific 
user. Sessions allow the server to keep track of activities by a single user. 

HTTP header functions 

There are two vital HTTP header functions, both of which you will need to get to 
know. 



Chapter 6: PHP's Built-in Functions 159 

HEADERO 

If you are going to be communicating with the browser or with other HTTP servers, 
this is the function to use. 

int header (string string) 

Essentially, you can send any header that would be expected under RFC 2616 
(ftp://ftp.isi.edu/in-notes/rfc26l6.txt). The RFC itself is a handful (and 
perhaps the sleepiest reading you'll do all year). Here is a common header you are 
likely to send. 

header("Location:http: //www. php. net" ) ; 

This is nothing more than a redirect: it sends the browser to a page you specify. If 
you have been working with straight HTML and the <meta type=refresh> tag or 
JavaScript to do your redirects, you should switch to this type of header whenever 
possible. It will work for all browsers and the redirection will be totally transparent 
to the user. 




IMPORTANT: No, make that VERY IMPORTANT. You cannot send a header 
after anything — ANYTHING — has been sent to the browser. If you send a 
header after even a hard return, there will be an error. If there is a hard return 
before your opening <?php tag, you will get error. If there is a hard return in 
an included file that precedes your he a der( ) function, you will get an error. 
This should not be a problem you encounter frequently; your pages should 
be designed so that most of the logic is handled prior to the display. 
However, if you have a situation you just can't work around, take a look at the 
output buffering functions. 



SETCOOKIEQ 

This is basically a specialized header function, because a cookie is set by nothing 
more than a specific HTTP header. 

int setcookie (string name [, string value [, int expire [, string 
path [, string domain [, int secure]]]]]) 

The first argument will be the name of the cookie. The second will be the value. 
The expire value should beset with the time function. The following is a pretty typ- 
ical use of setcookie( ): 

setcookie ( "id" ,$id_val ,time( ) + ( 24*60*60) ,"/"," .domai n . com" ,0) ; 



160 



Part II: Working with PHP 



This will set a cookie that will expire in 24 hours (24x60x60). The cookie will 
be available to every directory within domain.com. It you want to restrict it to a 
specific directory, you could change the / to a directory name. 



You can find more on cookies in Chapter 4, in the discussion of variables. 





In some versions of Internet Explorer, you must either give both time and 
path values or neither. 



HEADER_SENT() 

This function can keep you from sending headers after some text has been sent to 
the browser. 

boolean header_sent( voi d) 

If you are relying heavily on this function, you are probably not coding your 
pages properly. 

Mail function 

If you have Sendmail or another suitable email program installed on your system, 
this function will take all of the fuss out of sending e-mail from your PHP pages. 



Sendmail isthe program most commonly used with PHP's Mail function, but 
qmail with Sendmail wrappers will work, and on Windows, apparently 
Pegasus (http://pegasus.usa.com/) can work (though we haven't 
tested it). 




MAIL() 

This sends an e-mail from your PHP script. 



bool mail (string to, string subject, string message [, string 
additional_headers]) 



Chapter 6: PHP's Built-in Functions 



161 



Your basic e-mail will look like this: 

mai 1 ( "name@domai n .com" , "Subject Text", "The Complete message goes 
here" ) ; 

And if you want to get a little fancier and include a From and a Cc, use the fol- 
lowing: 

mai 1 ( " jay@trans-ci ty. com" , "Test Message", "Here I am", 
"From: Jay G\r\nCc: webmonkey@trans-ci ty . com\r\nRepl y-to : 
myname@myd.omai n . com" ) ; 

Additional headers have been added in the fourth argument, and the different 
headers are separated by linefeeds and newlines (\r\n) . 




If you want to set up a large e-mail system, don't use PHP. There are better 
tools out there. This function is intended for sending an occasional e-mail 
from within PHP scripts. 



If you'd like to send attachmentsin yourPHP e-mail, check outthis excellent 
article at PHPbuilder.com:http: //phpbuil der.com/col umns/kartic 
20000807. php3. 




URL functions 

If you've even looked at a querystring, you may have noticed that the text you 
entered into your form fields has been changed. For examples, spaces are turned into 
plus signs (+) and ampersands (&) are turned into %26. There are many other charac- 
ters that are encoded. (All non-alphanumeric characters other than hyphen (-), 
underscore (_) and dot (.) are replaced by a percentage sign and two characters). 

There will be occasions when you need to encode or decode text. For that you 
will use the functions below. 

URLENCODEO 

This function will encode a string so that it's URL ready. Most often you will use 
this if you want to send variable information to another page. 



string urlencode(string) 



162 Part II: Working with PHP 

For example: 

$myvar="this string with weird &* stuff"; 

lencoded = url encode( $myvar ) ; 

header("Location: http: //www.mydomai n.com?var=$encoded"); 

Notice that this code snippet has only encoded the values of a querystring ele- 
ment. If you were to urlencode the entire URL, you would not be happy with the 
results. The result of this code 

url encode(" http: //www.mydomai n . com" ) ; 
is "http%3A%2F%2Fwww.mydomain.com". 

URLDECODEO 

This function undoes the encoding process. It's usually unnecessary because the 
variable created from your GET or POST data is decoded in your variables. 

string url decode(stri ng) 

RAWURLENCODEO 

Returns a string in which all non-alphanumeric characters except hyphen, underscore 
and period have been replaced with a percent (%) sign followed by two characters. 

string rawurl encode( stri ng) ; 

This is the encoding described in RFC1738 for protecting literal characters from 
being interpreted as special URL delimiters, and for protecting URL's from being man- 
gled by transmission media with character conversions (like some e-mail systems). 

RAWURLDECODEO 

Unencodes according to the same provisions as rawurl encode( ). 

string rawurl encode( stri ng) ; 



Output buffering 



Output buffering is the process of writing the results of your script to a temporary 
buffer. Instead of being sent out over the Web it will gather in a buffer, where you 
can manipulate it if you wish. 



Chapter 6: PHP's Built-in Functions 163 

Probably the most common use of output buffering is to ensure you don't get 
errors caused by sending headers after text has been sent to the browser. To prevent 
this, you could start a buffer, write some of an HTM L page to the buffer, and then, 
given a specific condition, write a header (maybe a cookie), and then output the rest 
of the page. When you flush the buffer, the contents will be written to the browser 
without error. 



Ti 

am 


vJ? 


% 





If you are frequently using buffering to prevent headers from causing errors, 
rethink your page logic. Decisions first, output second. 



People have also been playing with using output buffering to gzip page contents. 
Then, in browsers that are capable of unzipping, the page could be downloaded a lot 
faster. However, given browser craziness, we wouldn't recommend this. 

BUFFERING FUNCTIONS USED IN THE BOOK 

There are quite a few object buffering functions. We used very few of them. 

OB_ START() This starts the buffer. 

void ob_start( void) 
FLUSH!) This clears the buffer. 

void flush(void) 

BUFFERING FUNCTIONS NOT USED IN THE BOOK 

Check the manual for some more sophisticated buffering functions. 

INFORMATION FUNCTIONS 

These functions will give you information about the environment in which you are 
working. 

PHPINFO!) Your guide to all that is available in your PHP environment. Use it. 
Use it. Use it. And then take it off your system. No point in letting crackers get a 
look at the specifics of your system. 

PHPVERSION!) Returns only the version of PHP you are using. 



164 Part II: Working with PHP 



Summary 



As you've seen, PHP has more functions than you will be able to commit to mem- 
ory any time soon. It can seem intimidating, but the quantity and quality of these 
functions are what make PHP such a great language. Most anything you need to do 
can be done quickly and painlessly. 

At first, you may need to study and play with the functions in order to get them 
to work. But in time, it will get a lot easier. You'll be making use of more and more 
functions, and keeping your scripts more compact and easier to read. 



Chapter 7 



Writing Organized and 
Readable Code 



IN THIS CHAPTER 

♦ Keeping your code tidy 

♦ Understanding the power and convenience of functions 

♦ Using object-oriented code 

♦ Learning the importance of comments 



This chapter presents a run-through of the preferred ways to present and organize 
your code. Along the way you will see how to construct functions and classes in 
PHP. By the end of this chapter, you should have a good idea of how write efficient, 
readable applications in PHP. And you should be ready to dive into the applications 
in Parts III and IV of this book. 



Indenting 



If you have done coding in any language, this point should be pretty obvious. But it 
is an important point, and therefore deserves some mention. In the type of coding 
needed for Web applications, following a few indenting rules could help make your 
life a little easier. 



How far should you indent? Some feel that each level of code should be 
indented by three spaces. Others, like us,think a single tab is the way to go. If 
you use spaces, it is possible that your code will look terrible in another text 
editor (maybe the one used by your co-worker). We really believe tabs are a 
better choice anyway. 




165 



166 Part II: Working with PHP 

Code blocks 

The most obvious use of indenting comes in differentiating blocks of code. For 
instance, it is fairly typical to have an if block within a while loop. 

$i = 0; 

while ($i < 100) 

{ 

$ i ++ ; 

if ($i < 50 ) 

{ 

echo "Within the first 49."; 

) 

el se 
{ 

echo "Between 50 and 99."; 

) 
} 

As you can see in this PHP code, each block is delimited by curly braces ({ )); 
this goes for both loops and if blocks. When a block is entered with an opening 
curly brace, the next line should indented. Each line following at the same level of 
execution should be indented at the same level. Additional nested blocks should be 
indented another level. 

Looking at the preceding brief snippet of code, it is easy enough to see that there 
are three distinct blocks. This may not seem like such a big deal with a small bit of 
code like this, but as scripts get longer, and levels of nesting get deeper, you will see 
how important this is. We're not going to belabor this point, because it should be 
pretty clear. But, for a quick example, we will re- present the previous code without 
indents. Note that it will work just fine— PHP doesn't care if you don't write your 
code neatly. But imagine coming back to this a month after you wrote it and having 
to troubleshoot or add code. Life would be a lot easier if you could easily find the 
block that needs work. 

$i=0 

while ($i < 100) 

{ 

$ i ++ ; 

if ($i < 50 ) 

{ 

echo "Within the first 49."; 

) 

el se 



Chapter 7: Writing Organized and Readable Code 



167 



echo "Between 50 and 99." 



Are you getting a parse error you can't identify? Make sure you have an 
identical number of opening and closing curly braces and parentheses. If 
you have, say, five closing curly braces in a page and only three opening, you 
haven't closed one of your code blocks. 



Ti 

ism 


P# 


% 


5Sf 



Function calls 

Indenting code should not stop at code blocks. Often you will need to use nested 
function calls or complex variables that take up several lines. You will be much 
happier in your coding life if you use indents in these situations. Take a look at the 
following, which is borrowed from the Catalog application. 

$file_ext = strtolower( 
substrdfile 

, stnnpos( $f i 1 e, " . " ) 



The purpose of this code is pretty simple: it takes the name of a file and assigns its 
extension (the characters following the final dot (".")) to $file_ext. It takes three 
separate built-in PHP functions to get this done. PHP will execute the innermost level 
first. There, stnrpos( ) finds the numeric position of the final dot. For instance, in the 
stringmyfile.jpg, it would return 6. Then the substn( ) function would return only the 
characters following the dot. Finally, that string would be set to lower case characters. 

This could be written on one line, but as you can see, it becomes rather difficult 
to read. 

$file_ext = strtol ower( substn( $f i 1 e , strnpos ( $fi 1 e ,"."))) ; 



Or maybe you find this easier to read. A lot of things we'll talk about in this 
chapter are matters of personal preference. The important thing is that you 
spend a lot of time considering how to make yourcodeas readable as possible. 




168 



Part II: Working with PHP 



In the first example of this code, it's much easier to see what each of the closing 
parentheses relate to, and you can more quickly get an idea of what this code 
accomplishes and how. 




You might be tempted to write the code above using temporary assignments 
to variables. Something like: 

$file_ext = strr_pos( $f i 1 e , "."); 

$ ext_letters = substr( $f i 1 e , $fil e_ext) ; 

$1 ower_ext_l etters = strtol ower( $f i 1 e_ext) ; 

But this is a good deal slower. Variable assignments do take time, and in a 
short piece of code where they aren't necessary, stay away from temporary 
variable assignment. That said, you should never sacrifice readability. In 
some places temporary variables can help make code much easier to read. 



SQL statements 

In Web database applications, SQL statements will be interspersed throughout PHP 
code. Usually PHP variables will be included within SQL statements to get specific 
results based on variable data. Indenting SQL statements will help keep the code read- 
able and maintainable. In the following, we show a few examples of SQL statements 
of various types. You will see many examples of these in the applications in Parts III 
and IV of this book. 

Ill table select 

$query ="select n.fname, n.lname, c.co_name, c. co_address , 
c . co_zi p 
from names n, companies c 
where n.name_id = $name_id 
and n.co_id = $c.co_id 



//update query 
$query="update products 

set product = 'Iproduct' 

, description = '$cleandsc' 
, price = $nullprice 
, image_src = $nul 1 image_src 
where product_id = $product_id 
"); 



//insert query 

$query="i nsert into products 



Chapter 7: Writing Organized and Readable Code 



169 



(category_i d , product) 
values ( $category_id , '$product') 



"); 




We've heard stories of database engines refusing to process queries, like the 
ones above, that have newlines in them. This is not a problem with MySQL 
and won't be a problem with most databases. However, there are other 
perfectly acceptable ways to write queries. Here are a couple of examples: 



Squery = "select col_l, col2 "; 
$query .="from table_l, table_2 
$query .="where col_l = $var"; 



or: 

$query 



'select col_l, col_2 " 

."from table_l, table_2 
."where col_l = $var"; 



Choose whichever you like best. 



Includes 



Every language has a facility for including external files. PHP has four commands 
that accomplish this. Before we get to those, we will briefly discuss why includes are 
so critical for writing organized and readable code. We'll start with a very common 
example. 

In most Web sites, header information will vary very little from page to page. 
There are opening tags (<html>, <head>, etc), and perhaps some navigation informa- 
tion. The following is a typical header to an HTM L page. 



<HTML> 
<HEAD> 

<TITLE>My Page Name</TITLE> 
</HEAD> 
<body bgcolor="#FFFFF" 1 ink="#8e0402" vl ink="#20297c"> 

It would be an absolute waste to type this text into every file within a Web site. 
Moreover, it could be a real pain. Say you wanted to change the bgcolor attribute 
of the <body> tag throughout the site. If this information were hard-coded in every 
file, you would have no choice but to go into each file individually and make the 
change, or write a script that would do it for you. 



170 



Part II: Working with PHP 



You are far better off keeping all of this information in a single file (maybe 
called header.php) and then using a command that will spit the contents of that 
into the file being accessed. For this, you would use one of the PHP functions dis- 
cussed in the following section. For this example we will use incl ude( ). 



If you have access to your Apache httpd.conf file you will probably want to give 
your include files a d istinct extension; .inc is a typical choice. Add itionally, if pos- 
sible, you will want to keep your includes out of the htdocs directory, so that 
they can notbe accessed byaURLWedid notuse either of these techniques in 
this book because most who use ISPs to host their PHP/MySQLapplications will 
not be able to alter their setups in this way. 




Let's say we have two files, header.php and index. php. Notice that we have made 
an important change in header.php: the <ti tl e> tags now contain a PHP variable 

<HTML> 
<HEAD> 

<TITLE> <?php echo $page_title; ?> </TITLE> 
</HEAD> 
<body bgcolor="#FFFFF" 1 ink="#8e0402" vl ink="#20297c"> 

Now for the index. php file. 

<?php 

$page_title = "Welcome to My Site"; 
include( 'header.php'); 

echo "Here are the contents of my PHP pages. Anything could be here."; 

?> 

Notice that the variable $page_titie will be picked up in the include. So when 
the index. php is served, the source code of the PHP page will be as follows: 

<HTML> 
<HEAD> 

<TITLE> Welcome to My Site </TITLE> 
</HEAD> 
<body bgcolor="#FFFFF" 1 ink="#8e0402" vl ink="#20297c"> 



Chapter 7: Writing Organized and Readable Code 



171 



Any code, whether HMTL or PHP, that is needed in a variety of pages should be 
kept within include files. Header and footer information, database connection code, 
and pages that contain functions or classes are all good candidates for includes. 



At the start of an include, PHP reverts to HTML mode. If code within your 
include needs to be parsed as PHP, you must first indicate that with the 
<?php marker. 




Once again, PHP4 contains a variety of commands that do slightly different 
things with includes. We will look at these commands in the following sections. 

includeO and require!) 

These commands are very similar and can usually be used interchangeably. 
However, you should know what distinguishes the two, as at times using the wrong 
one can cause problems. 

The require( ) command imports the content of the specified file even if the file 
is not used. This means, for starters, that even if the requi re( ) is within an if block 
that tests false, the outside file will still be included. You can probably deduce that 
this isn't such a big deal because code within a block that tests false won't be 
executed. However, it does take time for PHP to do the import, and there's no need 
to add execution time to your script by placing a require within an if block that 
could test false. 




There are other differences between include and require listed in the PHP 
manual, but these are more relevant to PHP 3. PHP 4 is pretty smart about 
handling includes and decides for which isthe best foryou when it interprets 
your script. It is very difficult to write a script that will behave differently using 
include and require. 



The inci ude( ) command will work better in a situation like the one above. An 
inci ude( ) command will be executed each time it is encountered within a script. 

include once() and requireonceO 

In addition to includeO and required, PHP provides inci ude_orce( ) and 
requi re_once( ). These are provided to keep you, the developer, from stepping on 
your own toes. As you might expect, they keep you from including the same file 
twice, which could cause some problems when it comes to calling user-defined 
functions. 



172 Part II: Working with PHP 

For example, say you had a file that contained a function, but that the function 
relied on another function from an outside file. You'd do something like this: 

requi re ' hel pf ul_f i 1 e . php ' ; 

another_f uncti on( ) ; 

{ 

f uncti on_f rom_hel pf ul_f i 1 e( ) ; 



Say we name this file short_function.php. If we needed both short_function.php 
and helpfulfi le.php in a third file, we could have a problem. All functions in helpful_ 
file.php would in fact be included twice. If we called one of the twice- included 
functions, PHP would not be able to resolve the ambiguity and would spit out an 
error. So in cases like this, use inciude_once( ) or requi re_once( ). Note that if files 
are included more than once we might also have a problem dealing with variables 
that inadvertently overwrite each other. 

Note that incl ude_once( ) and requi re_once( ) inherit behavior from include( ) 
and requi re( ): requi re_once( ) will only be processed once, and incl ude_once( ) 

can be executed multiple times within loops. 



User- Defined Functions 

In Chapter 6 you saw many of the functions built into the PHP processing engine. 
If you are a humble person and look at Appendix E or visit the online PHP manual, 
you should be duly impressed by the quantity and power of PHP's built-in 
functions. But it isn't enough —and no matter how much work the able developers 
put into the language, it never will be enough. That is because every developer on 
the planet has unique needs. We need to accomplish specific tasks, and we need to 
do it in ways that fit our own styles and predilections. 

User- defined functions allow us developers to create blocks of code that achieve 
specific tasks. The great thing about user-defined functions is that the code 
becomes reusable. Any piece of code that you find yourself writing over and over 
should be committed to a function. There's little doubt that it will save you time in 
the long run. 



In the applications presented in this book nearly all of the code is within 
functions. The files that you see in your browser are very much an assem- 
blage of function calls.Asyou will see, this helps to keep things readable. 




Chapter 7: Writing Organized and Readable Code 173 

Function basics 

We'll start by writing a simple function that writes out the start of an HTML table. 

function start_tabl e( ) 
{ 

echo "<table border=l>\n" ; 



To call this function within my PHP page, we would access it just like a built-in 
PHP function: 

start_tabl e( ) ; 

That's easy enough. But what if we want the border to vary in given situations? 
We could make the border a variable, and then in the function call specify the value 

for border. 

function start_tabl e( $border) 
t 

echo "<table border=$border>\n" ; 



start_tabl e( 1 ) ; 

Now say that most of the time we want the border to be 1, but would like to be 
able to change the border within the function call. The following would do the trick: 

function start_tabl e( $border=l ) 
t 

echo "<table border=$border>\n" ; 



Here$border has been given a default value of 1. But we can overwrite that value 
by specifying a different value when calling the function. For example, if we were to 
call the function with the following command, the table would have a border of 2: 

start_table(2) ; 

Once again, 1 is the default value, so if this function is called with the following 
code, the table border will be 1: 

start_tabl e( ) ; 



174 



Part II: Working with PHP 



If you know your HTM L, you know that the table tag can have multiple attributes: 
cellspacing and cellpadding are two others. Wecould add those to the function, along 
with default values. 

function stant_tabl e($bonder=l , $cel 1 spaci ng=2 , $cel 1 paddi ng=2) 
{ 

echo "<table border=$border eel 1 spaci ng=$cel 1 spaci ng 
cellpadding=$cellpadding>\n"; 



Then, in the call to this function you could alter any of these: 

start_table(4,2,5); 

The table created with this command would have a border of 4, cellspacing of 2, 
and cellpadding of 5. 



The values that the function will accept are known as arguments. So the 
start_tabl e function shown here will take three arguments. 




When constructing functions you should be aware that if you wish to change 
one of the default values in your function call, you must specify all the arguments 
that precede it (to the left). For instance, the first command in the following code 
will produce an error. However, the second one will work and will create a table tag 
with a border of 4, cellspacing of 3, and cellpadding of 2. 

//this will cause an error 
start_tabl e( ,5,5); 
//this will work 
start_table(4,3) ; 

Functions can accept more than simple variables; you can pass any of the scalar 
types (string, integer, double), any array (numeric, associative, or multi-dimensional), 
or objects. You might want to make use of a function that turns an array into HTM L 
unordered list, using <ul> and <li>. 

function create_ul ( $array ) 



echo "<ul>\n"; 

while(list( , lvalue) = each ($array)) 



Chapter 7: Writing Organized and Readable Code 175 



echo "<li>$value</li>\n"; 
} 
echo " < / u 1 > \ n " ; 



Returning values 



Of course your functions will do more than print HTML. Functions may perform 
database calls or mathematical computations or do some string handling. They can 
do just about anything, and often you will want to make the rest of the script aware 
of the results of your function. You can do this by using the keyword return. When 
a function hits the word return it leaves the function, and it will return whatever 
you specify— a variable, a boolean value (TRUE or FALSE), or nothing at all, if 
that's what you prefer. 

function basi c_math( $val_l , $val_2) 
t 

$added = $val_l + $val_2; 

return $added; 



You could then call this function and print the results. 

$added_value = basi c_math( 5 ,4) ; 
echo $added_val ue ; 

If fact, the following would work equally well: 

echo basi c_math(5 ,4) ; 

Functions can return any variable type (strings, object, arrays, and the like), or, in 
the case of database calls, they can return result identifiers. Additionally, functions 
can return FALSE. If you read Chapter 5, you may remember that in PHP, any non- 
zero, non-false value will be evaluated in an if statement as true. So we might want 
to improve the previous function by making sure the values passed can be added. 

function basi c_math( $val_l , $val_2) 
{ 

if (!is_int($val_l) || ! is_int($val_2) ) 

t 

return FALSE; 

} 

$added = $val_l + $val_2; 

return $added; 



176 



Part II: Working with PHP 



If either of the arguments in the call to this function is not an integer, the function 
will return FALSE and stop. A call to this improved function might look like this. 

f ( ! ( $added_val ue = basi c_math(7 , 5))) 

echo "What exactly are you doing?"; 

el se 

echo $added_val ue ; 



If the function returns a value (any value), that value will be added. If not, a 
special message will be printed. Notice how this mimics the behavior of many of 
the PHP built-in functions. Its purpose is to perform a task, and if it fails to do so, 
it returns false. 

Actually, there's something else that needs to be pointed out in this function. 
What will happen if the sum of the arguments sent to the function equal zero, say 
-1 and 1. In a case like that, your function will return 0. And, as we discussed 
in Chapter 5, is evaluated as FALSE in PHP. So you need to be careful about 
situations like this. You may run into situations like this with some of PHP's string 
handling functions, strpos( ), for example. You might be better off evaluating a 
call to the previous function like this: 

if ( ! i s_i nt( $added_val ue = basi c_math( -1 , 1 ) ) ) 

Take a quick look at the following function. We think it's a good example of how 
functions can really save you time, headaches, and keystrokes. The mysqi_query 
function is fine; it sends a query from PHP to MySQL and, if it succeeds, returns 
a result identifier. If it fails, however, it does not automatically return any error 
information. Unless you do a bit of digging, you won't know what the problem was 
with the query. So for every query in your applications (and there will be plenty) 
you tack on an or die phrase: 

mysql_query ( "sel ect * from table_name") or die 
("Query failed:" . mysql_error( ) ) ; 

But life gets quite a bit easier if you create a function like the following, and 
then send all of your queries through that function. 

function safe_query ($query = "") 
{ 

if (empty ( $query) ) ( return FALSE; ) 

Iresult = mysql_query ( $query ) 
or die("ack! query failed: " 



Chapter 7: Writing Organized and Readable Code 177 



"<li>errorno=".mysql_errno() 
"<1 i >error=" .mysql_error( ) 
"<1 i >query=" . $query 



return $result; 



So your applications might include a file with this function on every page, and 

then use safe_query( ) in place of mysql_query( ). 

Using a variable number of arguments 

One nice feature of PHP 4 is that you can pass an indefinite number of arguments 
to a function and then assign the list of arguments to an array. Consider the follow- 
ing code: 

function p r i n t_i n p u t_f i e 1 d s ( ) 
t 

$fields = f unc_get_args( ) ; 

while (1 i st($i ndex, $f i el d) = each( $f i el ds ) ) 

t 

print " < t r > \ n " ; 
print " <td valign=top 
align=right><b>".ucfirst($field).":</b></td>\n"; 

print " <td valign=top al i gn=l eftXi nput type=text 
name=$field size=40 val ue=\" " . $GL0BALS[$f i el d] . "\"></td>\n" ; 
print " </tr>\n\n"; 



start_tabl e( ) ; 

pri nt_i nput_f i el ds( "name" , "1 ocati on" , "emai 1 " , "url " ) ; 

end_tabl e( ) ; 



The GLOBALS array is discussed later in this chapter in the "Variable Scope" 
section. 



This function prints out form fields within a table. First, using func_get_args( ) 
creates an associative array, with the name of the argument as the key. Then each 
form field is printed out. This is pretty convenient because we can call a function 
in a number of situations and vary the output by including as many arguments 
as needed. 




178 



Part II: Working with PHP 



If you're wondering how this would work if your function contained some 
required parameters prior to the set of arguments that might vary, good for you: it's 
an excellent question. 

There are two other PHP functions that will work in such situations: f unc_num_ 
args( ), which returns the number of arguments sent to a function, and func_get_ 
arg( ), which returns a specific argument based on its numeric index, starting at 0. 
So, for example, you might have a function that prints an HTM L form with a variable 
number of input fields. 



function pri nt_form( $acti on= 



fmethod="P0ST": 



if (empty ( $acti on) )( return FALSE;) 

echo "<form acti on=$acti on method=$method>" ; 

$numargs = f unc_num_args( ) ; 

for ($i = 2; $i < $numargs; $i++) 



echo "<input type=text name=" . f unc_get_arg( $i 
} 
echo "</f orm>" ; 



">* 



pri nt_form( "myurl . php" , "", "myfieldl", "myfiels2"); 

Variable scope 

To work with functions you need to understand how PHP handles variable scope. 
Scope is an important topic in any programming language, and PHP is no different. 
In PHP, variables assigned outside of functions are known as global variables. 
These can be variables that you create, they can come from HTML form elements 
through either GET or POST, or they may be any of the variables inherited from 
the Apache environment. All globals are accessible from an array known as 
$GLOBALS. You can add to and delete from this array. 




We've said itbefore,and we'll say it again:usephpinfo( ) to get information 
about variables in your environment or your configuration. 



In PHP a global variable is not automatically available within a function. If you 
want to use a global within a function, you must indicate within the function that 
the variable you are accessing is a global. 

Here is an example of using a global within a function: 



function add_numbers( $val_2; 
{ 



Chapter 7: Writing Organized and Readable Code 179 



global $number; 

echo $number + $val_2; 



Snumber = 10; 
add_numbers(5: 



This code will print "15". Here $number is a global because it was assigned outside 
of a function. Using the keyword global tells PHP that we want to fetch the specified 
number from the $GLOBALS array. This code could also be written like this: 

function add_numbers ( $val_2) 
t 

echo $GLOBALS["number"] + $val_2; 
} 

Snumber = 10; 
add_numbers(5) ; 

In the applications in this book we will be using the technique shown in the first 
example, because it seems a little cleaner. It's nice to see where your variable is 
coming from at the top of the function. 

Within your functions, you may wish to make variables available as globals. 
That way they will be available in the body of your script and in other functions. To 
accomplish this, you must explicitly assign the variable to the $GLOBALS array. 
Here's a quick example: 

function assi gn_to_gl obal ( $val_l , $val_2) 
t 

global $sum; 

$sum = $val_l + $val_2; 



assi gn_to_gl obal (5,6) ; 
echo $sum; 

This script will print "11". For something a bit more complicated, we'll borrow a 
function from the applications section of the book. 

function set_resul t_vari abl es (Iresult) 
t 

if (!$result) ( return; ) 

$row = mysql_fetch_array($resul t ,MYSQL_ASSOC) ; 

while ( 1 i st($key , $val ue) = each($row)) 




180 Part II: Working with PHP 



global $$key; 
$$key = $val ue ; 



This function expects a result identifier gathered from mysqi_query( ). Assume 
that the query run prior to this function call will return a single row. That row is 
then assigned to an associative array named $row. Then each column taken from the 
query (which is now the key in the associative array) and its value will be available 
as a global. This could be useful if the values retrieved from the query are needed in 
many other functions. Note the use of variables in this code. See Chapter 5 for an 
explanation of this concept. 



All of the Apache variables ($D0CR00T, $REQUEST_URI, and so forth) are 
available as globals. To use them within functions you will need to make the 
function aware of them. 



USING GLOBAL VARIABLES 

Throughout the applications in this book you will see that global variables are used 
sparingly within functions. This is because it is just easier to keep track of your vari- 
ables if you are passing them through arguments and retrieving them through return 
values. If you start using globals extensively, you may find that your variables are 
returning unexpected values in different places— and finding the functions that are 
causing the error can be a major pain. 

Here's another reason to avoid globals when possible: You will be using the 
same variable name over and over and over again. We don't know how many times 
in these applications the variable names $query, $resuit, $row, or $i are used, 
but trust us when we say they are used frequently. All kinds of hassle would be 
introduced into my life if we had to keep track of each time we used one of these 
variable names. 

At times you will have little choice but to use global variables, but before you do, 
make sure that you can't accomplish the same thing using variables of local scope. 



Object-Oriented Programming 

A few years back there was a large move toward object-oriented programming. 
Some people just thought that the procedural approach —that is, coding strictly 
with functions— just wasn't enough. Therefore, the folks working on languages like 
C++ and J ava popularized an approach that allows a developer to think about code 
in a different way. 



Chapter 7: Writing Organized and Readable Code 181 

The idea behind object-oriented programming is to think of portions of your 
application as objects. What is an object? Well, it's an amorphous thing, a kind of 
black box— a superstructure of variables and functions. But if you are new to the 
concept, this description may not be so clear. 

To make things clearer conceptually, we'll give an example that comes up later 
in the book. Chapter 10 presents a catalog, a place where your business could post 
a listing of goods. In this application, there is an organizational breakdown. At the 
highest level there is a list of categories, and each category will contain one or 
more products. Knowing this, you could think about a category as an object. 

As this is a catalog, you might expect certain things in a category. A category 
would probably have a name and a caption; it might also have a unique ID number. 
There might also be other parameters that further describe the category object. 

In addition to these descriptive items, there are certain actions you will want to 
perform on a category. Actions include things such as saving a new category, deleting 
an existing category, or maybejust printing out a specific category. 

Now all you need is the correct nomenclature. These descriptions of the object 
(such as name and unique ID) are called properties, and the actions (save, delete, 
print) are known as methods. 

Before we get to using objects, we want to explain a couple of advantages to this 
object-oriented approach. Say some programmer has created an object for cate- 
gories and tells you about the methods and properties. You don't really need to 
know how any of it works; you just need to know that it does. You can make use of 
the methods and properties of the object in your scripts with little effort. 

Of course, the same could be said of a well-designed procedural approach. A 
well-documented collection of functions could work equally well. In fact, many 
programmers dislike object-oriented programming, feeling it is unnecessary. There 
is no doubt, you should be able to write good procedural code before you move on 
to objects. 

Using objects, not only can you make use of methods and properties in the heart 
of your scripts, you can extend the functionality of a class by making use of a 
concept called inheritance. Going back to the catalog example, a category will have 
one or more products. Thus every product will belong to a category. So a product 
will have its own set of properties and methods, but in addition, it will inherit the 
properties and methods from its category. 



If you are coming from a language like Java, you will need to be aware of 
some of PHP's shortcomings in this department.There is no way to desig- 
nate methods as "private"and there are no deconstructors. 



Classes 

In object-oriented programming, you will be working with classes. A class is a 
structure that encompasses your methods and properties. Within a class, properties 




182 



Part II: Working with PHP 



(items that describe the object) are variables. And methods (actions to be take on 
the object) are functions. Let's start with a simple class called Category. 




This is NOT the same set of classes used in the catalog application in Chapter 
10. For instructive purposes, the code here has been simplified. 



class Category 
{ 

//here are the properties 

var $category_name ; 

var $category_desc ; 

var $category_i d ; 

Here, at the start of this class, we defined the properties. Notice the use of var to 
declare each of the properties. You don't need to declare properties elsewhere in 
PHP, but in classes you must. Now we're going to create the first method. 

class Category 
{ 

//here are the properties 

var $category_name ; 

var $category_desc ; 

var $category_i d ; 

//gets all the properties for a give category„id 

functior Category ( $category_id=0) 

{ 

//expecting a category id when the object is 

1 1\ nstanti ated 

if ( $category_i d == 0) 

{ 

return ; 



$query = "select category_name , category_desc from 
categories where category_id = $category_i d" ; 

Iresult = mysql_query ( $query ) or 
d i e ( my s q 1 _e r r o r ( ) ) ; 



1 i st($category_name , $category_desc) 
mysql_fetch_array ( $resul t) ; 



Chapter 7: Writing Organized and Readable Code 



183 



ft hi s->category_name=$category_name; 
fthi s->category_desc=$category_desc ; 
fthi s->category_id=$category_i d ; 



There are a few things here that require explanation. First, notice that the 
method (i.e., the function) has exactly the same name as the class. That tells PHP 
that this is the constructor. A constructor is automatically run as soon as a class is 
instantiated. The other thing to note is the use of $this->. It indicates a property or 
method within the current class. Above, we are assigning values to the properties: 
categoryjd, category_name, and category_description. If we wanted to call 
a method, we would indicate that with parenthesis: $this->method_caii ( ) or 

$thi s->method_cal 1 ( $val ue) 

To continue with this example, we will add a method for deleting a category. 
Note that the method below assumes that the database contains two tables- 
one named categories and one named products— and that there is a one- to-many 
relationship between categories and products. Therefore, you would want to make 
sure that a category isn't deleted if it contains products. 

class Category 

t 

//all the stuff you saw above goes here 

function del ete_category ( $category_id=0) 



//make sure a category_id was defined in the 
//constructor or is indicated explicitly 
f ( $category_i d == && empty ( $thi s->category_i d) ) 

$this->error = "Nothing to delete"; 
return FALSE; 

/create where clause with the category_id 
/retrieved from the proper place 
f ( $category_i d !=0) 

$where_cl ause = "where category_id = 
$category_id" ; 

else 

$where_cl ause = "where category_id = 
$ t hi s->category_id" ; 



184 Part II: Working with PHP 



//make sure there are no products left for this 

//category 

Iquery = "select * from products $where_cl ause" ; 

Iresult = mysql_query ( $query ) or 

di e(mysql_error( ) ) ; 
if (mysql_num_rows ( $resul t )>0) 
{ 

$this->error = "There are 
still products for this category"; 
return FALSE; 
) 

$query="del ete from categories $where_cl ause" ; 
Iresult = mysql_query ( $query ) or 

di e(mysql_error( ) ) ; 
i f (mysql_affected_rows( ) == 0) 
{ 

$this->error = "There were no rows to delete"; 
return FALSE; 
} 

return TRUE; 
) 
} 

We now have a class with two methods, the constructor and del ete_category, 
and three properties. Now it is time to put this class to work. 



Instantiating an object 



Instantiating basically means "creating." Let's say we have a PHP page that is 
intended to delete categories. To make this work with the above class, we need to 
do a few things. First, we need to include the file containing the class. 

require "cl asses . php; " 

Next we have to create an instance of the object. We can do this with the key- 
word new: 

$c = new Category; 

Now, using the object $c, we can access any of the properties or methods using 
the same syntax seen in the previous example: $c-> property or $c->method( ). 

Now we want to put together a page that actually removes a record from the 
database. For the sake of this example, assume that a single $category_id has 
been passed via the querystring. 



Chapter 7: Writing Organized and Readable Code 185 



<? 

require classes. php; 

$c = new Category ( $category_id) ; 

if ( ! $c->del ete_category ( ) ) 
{ 

echo "Deletion Failed: $c->error"; 
} 

el se 
t 

echo "Deletion Succeeded for:"; 
echo " < u 1 > 

<1 i >$c->category_name 
<1 i >$c->category_descri pti on 
<1 i >$c->category_i d 
</ul> 



?> 



As you can see this makes for a pretty clean page, which everyone is looking for 
ultimately. 

Inheritance 

So far, we have worked with only one class. But one of the great advantages of 
object-oriented programming is that methods and properties from one class are 
inherited by another. We'll show a very quick example of this by writing a class 
called Product. Here's the shell: 

class Product extends Category 
( 

var $product_id; 

var $product_name ; 

var $product_pri ce ; 



Notice the use of the word extends. This tells PHP that all of the products and 
methods available in Category should be available in Product. If we wanted to call 
the deiete_category( ) method from within the Product class, we could call it 

with $this->del ete_category ( $category_id). 



186 



Part II: Working with PHP 




In PHP, constructor methods of parent classes are not automatically run 
when thechild class iscalled.Forexample.calling $p = new Product will 
not run the Category ( ) method. 



Now we'll add a quick constructor method: 

class Product extends Category 
{ 

var $product_id; 

var $product_name; 

var $product_pri ce ; 

function Product( $product_i d=0) 
{ 

//expecting a product_id to be passed 
if ($product_id ==0) 

{ 

return FALSE; 



Iquery = "select product_name , product_desc , 
product_pri ce , category_id from 
products where product_id = $product_id" ; 

$result = mysql_query ( $query ) ; 

1 i st( $thi s->product_name , $thi s->product_desc , 
$thi s->product_pri ce) = 
mysql_fetch_array ( $resul t) 

$this->Category( $category_i d) ; 



Notice that this method not only assigns values to the properties for this product, 
it also calls the constructor of the Category class, which then assigns values to those 
properties. Note that because of inheritance, we could write a script as follows: 

requi re 'classes. php ' ; 



$p = new Product($product_i d) ; 
echo $p->category_name ; 



You see that the properties of Category are available to the Product object. 




Chapter 7: Writing Organized and Readable Code 187 

Object- Oriented Code versus 
Procedural Code 

Here's the million-dollar question: In your applications, should you use object- 
oriented code or procedural code? This is another topic that inspires religious 
debate. But really there is no need for that because there is a correct answer: It 
depends. If you have an extensive background in object-oriented programming, 
and you are more comfortable coding classes, do that. However, if you dislike the 
way object-oriented code works, use only functions. 



There are a few large class libraries on the included CD-ROM, including 
some taken from Manual Lemos' impressive site http: //phpcl asses . 
upperdesign .com. His form creation and validation class is truly epic, but 
it may be a bit unwieldy for some. 



Object-oriented code comes with a couple of advantages and disadvantages. 
Weigh them and decide for yourself if you should use classes or just functions. 
Advantages: 

♦ You can save time using the object-oriented approach. 

♦ You can make highly reusable pieces of code. 

♦ You can make use of extensive class libraries available for free on the Web. 

Disadvantages: 

♦ It's slower than the procedural approach. 

♦ The syntax can be confusing at first. 

♦ Web programming does not make use of many of the advantages of 
object-oriented code. 

♦ If you're using very large class libraries, there may be a performance hit. 



Comments 



In any programming language, comments are essential —not only to you as you're 
writing the code, but to those who come to the code after you. What may be 
crystal-clear to you may be absolutely unreadable to others. Or, if you've had to do 
something particularly complex, you might find that you don't even understand 



188 Part II: Working with PHP 

what you were thinking if you come back to the code a couple of months after you 
wrote it. 

In PHP, you can indicate comments with two slashes (//), with a hash (#), or by 
surrounding commented code with /* and */. This last method is particularly helpful 
for multi-line comments. 

Comment all of your functions, what they do, what they are expecting, and what 
they return. Make sure to make note of any variables that could be tough to track. 

As you look through the /functions directory of the CD, you will see that every 
function has an initial comment that mimics the style used in the PHP manual. 
For example, 

int fetch_record (string table name [, mixed key [, mixed value]]) 

Then we provide some description as to what these arguments mean and the sig- 
nificance of the return value. When writing the body of the function, you should 
comment on anything that would not be intuitive to someone coming to the script 
at a later date. If you have a series of functions that perform some complex string 
handling or use lengthy regular expressions, make sure to note exactly what those 
functions are intended to accomplish. For example, we will reprise this line of code: 

$file_ext = strtol ower( substr( $f i 1 e , strrpos( $fi 1 e ,"."))) ; 

This isn't especially difficult to figure out, but you could sure help the next person 
coming to this line with a simple comment. 

//get characters following the final dot 

//and make lowercase 

$file_ext = strtol ower( substr( $fi 1 e , strrpos( $fi 1 e ,"."))) ; 

The other important thing to comment is the overall logic of pages, especially 
long pages. Often a script will behave differently in varying circumstances. 
Variables passed from forms, errors, and other factors will effect what portions of 
script will run. At the top of the page, you can indicate what factors will affect the 
page's logic and then as you reach different if blocks, explain where the conditions 
are coming from and what they mean. 

For a brief example, take the confirm_delete.php page from Chapter 8, 
Guestbook2k: 

/* 

his script will only run if the user is logged in. 

It will be accessed in two circumstances: 

-- fhe "Delete Entries" button was pressed on 

the edit.php page. (Normally this will be the 
first time to this script). The ids of the 
records to be deleted are passed in the entry_id 



Chapter 7: Writing Organized and Readable Code 189 



array. 
-- The "Confirm Delete" button was pressed on this 

page. This will comfirm the deletion and run the 

delete queries. 
*/ 

include(" header. php"); 
include("authenticate.php"); 

$page_title = "Confirm Changes"; 
include("start_page.php"); 

//if coming from edi t_page .php . The first time to 
//this script. Print a form with checkboxes that 
//the user must check in order to confirm deletion, 
if ($submit == "Delete Entries") 
( 

//the form contains no action, so it will submit 

//to the same page, and the value of submit will be 

//"Confirm Delete" 

print "<form method=post>\n" ; 

if ( i s_array ( $entry_id) ) 

{ 

while ( 1 i st($key , $val ue) = each( $entry_id) ) 
{ 

print "<li>Delete entry #$value?\n"; 
print "<input type=hidden name=\"entry_id[]\' ! 
val ue=\"$val ue\">\n" ; 



print "<br><input type=submit name=submit val ue=\"Conf i rm 
Delete\">\n" ; 

print "<input type=hidden name=offset val ue=\"$off set\">\n' 
print "</form>\n" ; 



//if the page has been submitted to itself, 
//to confim that the deletion should happen, 
elseif ($submit == "Confirm Delete") 
{ 

//loop through each element in the entry_id array 

//and run a delete query for each item. 

while (1 i st( $key , $val ue) = each( $entry_id) ) 

t 

print "<1 i >Del eti ng entry #$value\n"; 

$q = "delete from guestbook where entry_id = lvalue" 



190 Part II: Working with PHP 

mysql_query ( $q) or die( " Inval id query: $q"); 

) 
} 

el se 
{ 

print "No action to confirmXn"; 
} 

We will end this section on a word of caution: don't over comment. Commenting 
every single line, or making the obvious even more obvious is annoying. For example, 
the following comments are completely unnecessary and will only make your scripts 
difficult to read. 

//make string lowercase 
$str = strtol ower( $str ) ; 
//increase $i by 1 
$i++ 

Commenting calls for good judgement. You don't want to comment too much, 
you don't want to comment too little. My best advice is to take a look at how many 
programmers comment their code and pick a style that you like. We use one method 
for the applications in this book; others have different styles. 

The PEAR directory of your PHP installation is a great place to look for tips on 
good coding style. PEAR stands for PHP Extension and Application Repository. It is 
a growing set of scripts that contains a series of best practices for programming 
with PHP. The folks working on PEAR are real pros who write terrific code. We rec- 
ommend looking through the scripts in that directory to glean some tips on writing 
quality code. 



Summary 



In this chapter we have presented some ways to write clean and organized code. 
When you look at your scripts, you should ask yourself a few questions. Are there 
blocks of code that are common to every page? Maybe those blocks should be 
moved into an include. Are there chunks of code that I'm writing over and over 
again? Perhaps writing a function would savetime. Is the next person who comes to 
this script going to be able to figure out what I've been doing? If not, make sure that 
you add enough comments to make things clear. 

You will need to decide whether or not an object-oriented approach is good for 
you and the application you're writing. The fact is that if you're not immediately 
sure, the answer if probably no. Make sure you are comfortable writing clean pro- 
cedural code before you jump into classes. 

Now that you've done all the required reading, it is time to move into Part III, 
where we put PHP and MySQL to work. 



Chapter 8 

Guestbook 2000, the 
(Semi-)Bulletproof 

Guestbook 



IN THIS CHAPTER 

♦ Learning the power of guestbook 2000 

♦ Organizing your code in a reasonable way 

♦ Writing good, reusable functions 



In this chapter we will develop the first of our applications— a guestbook. 
Guestbooks aren't complex and they aren't very exciting. However, this application 
does give us the chance to introduce some concepts such as validation and put 
many of the practices discussed earlier in this book to work. 

In the Introduction of this book we introduced some code that could be used for 
the most basic guestbook possible. However, using that code for your guestbook is 
not a good idea. It's got all kinds of holes that will allow malicious people out there 
to mess with your pages. There is another problem with the ultra- basic guestbook: 
given the way the code is just dumped into one page, there's not a line that's 
reusable. One of the top goals of developing any application is to create chunks of 
reusable code. 

Determining the Scope and 
Goals of the Application 

The easiest way to get yourself into trouble when coming at an application is to not 
know exactly what you are trying to achieve. A vital part of the developer's job is 
to figure out exactly what is needed in the application. Usually this will involve 
extensive discussion with the people for whom the application is being developed. 
During these discussions, it is important to think a step ahead and ask questions 
that may not have been considered. What if the scope increases in a certain way? 

193 



194 



Part III: Simple Applications 



What if additional information but related information needs to be tracked? All of 
these things will affect the way you design your database and your scripts, and that 
is why it is best to know the exact scope and goals of your application. Depending 
on whom you're working with, you may want to get some sketches of pages that 
need to be developed. 

The scope of this application is small and the goals are minimal. The guestbook 
stores names, addresses and the like. (But, to tell the truth, the goal of this chapter 
is not so much to show you how to write a guestbook as it to show you how to 
write good, reusable, organized code for your applications.) In any case, you should 
know what guestbook2k looks like before you proceed. 



In this chapter, we're not going to take the notion of creating good functions 
as far as it can go. In the next chapter we'll present a more extensive set of 
functionsthat we'll use throughout the rest of the book. 




Necessary Pages 



There are three basic pages: one for signing the guestbook, one for viewing the 
guestbook, and one for administering the guestbook. 

Figure 8-1 shows the page that gives the user the opportunity to sign the 
guestbook. It's pretty simple, a form with four text fields and one textarea field. 
Additionally, there is a submit button and a reset button. 



-3 Sign My Guest Book!! - Microsoft Internet Explorer 



File Edit View Favorites lools Help 



Address |§ http: /Al 92. 1 68. 1 . 1 Vbook/guestbook2k/sign. php 



"3J 4*Go J Links 



<*■,-►, @ rn C 

Back Stop Refresh Home 



© a 

Search Favorites History 



Mail 



Print 



Edit 



Sign My Guest Book!! 



Name: T 

Location: T 

Email: f" 

Url:P 

Comments: 



Sign! | Start Over 



_ 



zi 



lastartl ft, <P Q ■ J3 <A ■! ■|[5±j 3Vff)°0'M <f /t-^% 4:31 AM 



Figure 8-1: Page for signing the guestbook 



Chapter 8: Guestbook 2000, the (Semi-)Bulletproof Guestbook 



195 



Next, there must be a way to see who has signed the guestbook. For the sake of 
having readable Web pages, we created a page, shown in Figure 8-2, where only two 
entries are printed on each page. At the bottom of the page are navigational ele- 
ments that indicate whether there are previous or additional entries. These should be 
conditional and should disappear appropriately when you are at the beginning or 
end of the guestbook. 



■=? View My Guest Book!! - Microsoft Internet Explorer 



M> 



J File Edit View Favorites lools Help 



Address |@ http://1 92. 1 68. 1 . 1 Vbook/guestbook2k /view. php?offset=2 



"_[ ^Go 



View My Guest Book!! 

Name: jayman 
Location: none to speak of 
Email: jgreen_l@yahoo.com 
URL:hhhhhh 
Entry Date: 24 July, 2000 10:31 AM 
Comments: aljdfl; 

Name: John block 
Location: 

Email: jgreen_l@yahoo.com 
URL: 
Entry Date: 24 July, 2000 10:31 AM 
Comments: 



«Previous Entries NextEntries» 



;ast 3 rt| @ <Sj q [gj ■ i; _i |]jg|e_j_|||iau'fft»P''MdB./t'p g B 4:37 am 



Figure 8- 2: Page for viewing the guestbook 

Finally, we need a page that enables you to delete entries you don't want. The 
page in Figure 8-3 seems to do the trick. Access to this page needs to be limited to 
authorized users. You don't want any old schmo going in and cleaning out your 
guestbook. 



What do we need to prevent? 



The major problem that we need to tackle here is one that is common to any appli- 
cation with form input. It is possible for vandals to input nasty code into your forms 
that will screw up the pages for everyone else who comes along. If you used the 
guestbook application in the Introduction you could be in serious trouble. Consider 
what would happen if someone inserted the following code into a text field: 



<script>alert("boo");</script> 



196 



Part III: Simple Applications 



-' Edit The Guest Book - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



Address __ hK P 7/1 92.168.1. 1Vbook/guesr.book2k/ediLphp 



~__J <^ Go 



Links : 



Email: pgreen_l@yahoo.com 



URL: |http://www.trans- city. com 



Comments: |l love you, man. 






Delete? 



\ !Yes, delete entry #1 



Name: |j ay Greenspan 



Entry: 



Date: 



Location: Sf 



Email: 



j gre en_l (_!y aho o . c om 
URL: http://www.ttim.5- city. com 



Comments: I love you, man 



\ Yes, delete entry #2 



Delete Entries | Reset 

:jasi a n| ^ <Sj *> & ■ is _i Uleaj _j||^u t g)°^%^j^^^>% 4:4oam 



Figure 8-3: Page for administering the guestbook 

The next time the page loaded, the viewer would be greeted with the little treat 
seen in Figure 8-4. 



3 ://www.madfish.com:8080/book/questbook/view.php - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



D 



Microsoft Internet Explorer I 



'guestbook/View. php 




~^\ &Go J Links 



Name:j 
Locatic 
Email:j 

URL:littj- ■■"-"-"* i-i-^t.-iou- .-.;.n, 
Comments :like, yo, this sucks 



Name:joe blow 

Location:kokomo 

Email:j o e@blo w . c om 

URL :http J /www] o eblow. c om 

Comments :like, yo, this sucks 



Name:joe blow 

Location:kokomo 

Email:j o e@blo w . c om 

URL :http J /www] o eblow. c om 

Comments :like, yo, this sucks 



:flst art | m <ji *> {M m in ^ [gj&l ±| 3Vff)»Q'M J^t-P^i 



_l 



4:44 AM 



Figure 8-4: Results of a problem entry 



Chapter 8: Guestbook 2000, the (Semi-)Bulletproof Guestbook 



197 



If some jerk felt like it, he could screw up your page with all sorts of tags. This 
code, for instance, would create the disaster seen in Figure 8-5. 

<img src=http: //www. br itney -spears. fsnet.co.uk/britney 4. jpg> 



3 http://www.madfish.com:8080/book/guestbook/view.php - Microsoft Internet Explorer HE1E3 



J File Edit View Favorites lools Help 



Address http: lf\ 92. 1 68. 1 . 1 /book/guestbook /view, php 



SS ante: Urittney Jan 

Location: 

Email: 

URL: 



— wm 

"31 &G° |J Links »| 



"3 




J 



fflStartj @ <Sj «P [5j ■ jj ^ |][fjjEJ TJ||^U¥)°^%^J^^Q% 4:47 AM 

Figure 8- 5: Another problem entry 



Additionally, this application requires some validation. When the user enters 
information, this application is going to see that it makes sense. The application will 
check for the following: 

♦ E-mail addresses should contain an at symbol (@), one or more characters 
before the®, and a dot somewhere after the®. E-mail validation can get 
more complex (and will in later chapters). 

♦ URLs should look like URLs, complete with an http:// prefix and at least 
one dot. 

♦ There must be some text entered in the name field. There's little point in 
a guestbook entry without a name. 

♦ No e-mail address should appear more than once in the database. 

Once the application has checked all of this, the user will need to be made aware 
of any errors. Figures 8-6 and 8-7 show how we will indicate these errors. 



198 



Part III: Simple Applications 



'3 Sign My Guest Book!! - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



Address |*S] http:/AI 92.168.1. 1/book/guestbook2k/sign.php 



~_ (E^Go 



o 

Links »| 



Sign My Guest Book!! 

■ jgreen_lyahooge.com doesn't look like a valid email address 

■ kdiek doesn't look like a valid URL 

Please try again 



Name: 

Location: 

Email: 

Url: 


Jay Greenspan 




I 




jgreen_1 yahooge.com 




kdiek 




Comments: 


] 



Sign! Start Over 



;ast 3 rt| @ni*>®m%si&L |][fjgj _||J%Wff>°0'M <§JW% 4:50AM 



Figure 8-6: Reporting bad information 



W Sign Mv Guest Book!! - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



> - *-@©ai®a0| 



Address |«S] http:/V1 92.1 68.1 .1 /book/guestbook2k/sign.php 



~_ &Z° 



Sign My Guest Book!! 

• jgreen_l@yahoo.com has already signed this guestbook. 
Please try again 



Name: 

Location: 

Email: 

Url: 


Jay Greenspan 




San Francisco 




j g re e n_1 @y ah o o . co rn 




I 




Comments: 


| 



: 



Sign! | Start Over | 
IJiStartl <*> & ■ J* _ Ji Mjj _|[i!Ut«»'ff%i^<fog J^iS^ 



_ 



9:31 AM 



Figure 8-7: Reporting duplicate entry 



Chapter 8: Guestbook 2000, the (Semi- )Bulletproof Guestbook 199 

Designing the Database 

We covered the normalization process in Chapter 1 and before long we'll put these 
normalization skills to work. For this application the set of information is pretty 
simple. So simple, in fact, that a single table will do the job. Actually, this isn't quite 
true. For administrative purposes, you should create a table against which user 
names and passwords can be authenticated. Here are the create statements that will 
make the tables. 

create table guestbook 
( 

entry_id integer not null auto_i ncrement , 

name varchar(40) null, 

location varchar(40) null, 

emai 1 varchar(40) null, 

url varchar(40) null, 

comments text null, 

created timestamp, 

remote_addr varchar(20) null 

, key guestbook_key (entry_id) 
); 



create table guestbook_admin 
( 

username varchar(50) not null, 

password varchar(255) not null 
); 

When adding a user to the guestbook_admin table, it would be best to encrypt 
the password. The easiest way to do this is by using MySQL's password function. 

insert into guestbook_admi n (username, password) 
values ('jay', password( ' rul es ' ) ) ; 

After you've run this command, the actual value stored in the password column is 
065cb7fl2ad99fe3. When you need to find out whether a user knows the password, 
you can use the password function again. 

select * from guestbook_admi n where 

username = 'jay' and 

password = (password( ' rul es ' ) ) ; 



200 



Part III: Simple Applications 



Code Overview 



In this, the first of your applications, you need to look at the architecture you 
will use in constructing your applications. The applications on the CD have been 
constructed so that they are as reusable and portable as possible. 

To start with, there is a folder named book, which should be copied to the root 
directory of your Web server. On Apache this folder is named htdocs by default. The 
book folder contains all the applications documented in this book. If you need for 
some reason to copy the book folder to a spot other than the root directory, you'll 
need to make sure to change the include commands at the top of header.php. 
Figure 8-8 shows the folder structure. 



lj Exploring -from brad 




HHH 


File Edit View Go Favorites 


loo Is Help 


o 


<*= T T E2 


<9% 


m a 


m 


X df 


m „ 


Back Up 


Cu 


Copy Paste 


Undo 


Delete Properties 


Views 


J Address |Q D:\from_brad 




d 


Folders 


X 


_J 








■■■T"! coffeefto 


- 








■■■T"! efaw 




book 








■■■Tn for apple 












■■■T"! for atornz 












1 for quru 










■■■Pi for zend 








- 


''JlffflMpNfrffi 








D UJ book 








C 


]■■{ \ cataloq 
















T-T^l content 
















1-Pl content, old 
















■■■■T^l functions 
















■T^l auestbook 
















]■■■ I questbook2k 
















]-Q netsloth 
















T-T^l survey 
















]-Q tracking 










□ HS EXP ELDR 








■Q ie5 


zi 






ijA Start] £ <Sd ^ \M ■ i* & 


4j M M^ 3Vff>«»0'M &St**>% 4:54 AM 



Figure 8-8: Application folder structure 



Within the book folder, there is a series of folders, one for each of the applica- 
tions presented here and one labeled functions. In this application we will concern 
ourselves with basic. php. This file will contain some functions that you will use in 
a variety of applications. We'll discuss the functions in basic. php that are used in 
guestbook2k in the section entitled "Code Breakdown." The code that is relevant 
only to guestbook2k is kept in the guestbook2k folder. Here, the functions that will 
need to be addressed across a number of pages are kept in the header.php file. We 
will also explain these functions in detail in the "Code Breakdown" section. 



Chapter 8: Guestbook 2000, the (Semi- )Bulletproof Guestbook 201 

The pages that are called from the browser are named intuitively: view.php, 
sign.php, and edit.php. Each of these pages calls start_page.php and end_page.php. 
These contain standard header and footer information and are easy enough to read, 
so they we won't discuss them here. 

You may find the view.php, sign.php, and edit.php files surprisingly short. They 
only contain a couple of dozen lines of code each. This is because just about every- 
thing is written in reusable functions. 

So once again the important thing is to understand the functions that are kept in 
/book/functions/basic. php and /book/guestbook2k/header.php. 



Code Breakdown 



As mentioned in the previous section, the vast majority of the work is done in func- 
tions, and these functions are kept in files that will be included in the pages called 
from the browser. 

Reusable functions 

Here we will cover the contents of function/basic. php and guestbook/header.php 

FROM FUNCTIONS/BASIC.PHP 

We can address these in any order— alphabetical seems as good as any. 

AUTHENTICATED This little function sends a 401 HTTP response code. This header 
forces your browser to open the username and password box shown in Figure 8-9. 

function authenticate ( $real m="Secure Area" 

, $errmsg="Pl ease enter a username and password" 



Header ( "WWW-Authenti cate: Basic real m=\"$realm\" " ) 
Header("HTTP/1.0 401 Unauthorized"); 
di e( $errmsg) ; 



The values entered into these text fields are set to PHP variables $php_auth_ 
user and $php_auth_pw. PHP can then query MySQL to check if the values are 
stored in the database. Note that this function merely sends the header. It does 
nothing to check the values entered in the text boxes. They are checked in the 
guestbook2k/authenticate.php file. This function is called if either no values have 
been entered or the values match nothing in the database. 

If the user hits the Cancel button the string stored in $errmsg is printed. 



202 



Part III: Simple Applications 



^ http://madfish.com:8080/book/guestbook2k/edit.php - Microsoft Internet Explorer 


J File Edit View Favorites lools Help 


Address |*S] http7/madfish.com:808uybook/guestbook2k/edit.php 










Enter Network Password B E 




®)2>y Please type your user name and password. 
Site: madfish.com 
Realm Guest Book Administration 


User Name 


Password 1 

I - Save this password in your password list 


j OK ] Cancel j 










lastartl mni*>&ml$f& Ule a| -f|||^W$ ) >°^%*lfflj>^V% 6:36AM 



Figure 8-9: Results of a 401 unauthorized header 




This type of authentication is available only if PHP is installed as an Apache 
module. If you are using PHP as a CGI, which is the only way to run it under 
Windows, this will not work. If you are doing some development work on 
Windows, go into the applications comment out the calls to authenticate!) 
and create an include for win_authenticate.php file. 



CLENUP_TEXT() This function goes a long way toward making sure we don't 
insert malicious text in our database. 

function cleanup_text (lvalue = "", lpreserve=" " , lal 1 owed_tags=" " ) 
{ 

if (empty ( Ipreserve) ) 

{ 

lvalue = stri p_tags( $val ue , $al 1 owed_tags ) ; 

) 

lvalue = html speci al chars ( $val ue) ; 

netunn lvalue; 



Chapter 8: Guestbook 2000, the (Semi- )Bulletproof Guestbook 203 

This function accomplishes two things. First, it removes all HTML tags. The 
strip_tags() function takes care of that. No need to worry about malicious 
Britney Spears pictures here— unless you want them. You can indicate tags you 
want to keep with the second argument ($allowed_tag). For instance if you wanted 
to allow bold and italic tags, the second argument to strip_tags() could be a string 
like this: "<bxi>". 

Then html_specialchars() changes ampersands and double quotes to their proper 
HTML entities (& and "). After being run through this little function, 
your text is ready to be inserted in the database. 

SAFE_QUERY() This function will save you from pulling your hair out when 
you're trying to get your queries right. 

function safe_query ($query = "") 
t 

if (empty ( $query) ) ( return FALSE; ) 
$result = mysql_query ( $query ) 

or die("ack! query failed: " 

."<li>errorno=".mysql_errno() 
. "<1 i >error=" .mysql_error( ) 
. "<1 i >query=" . $query 
); 
return Iresult; 



Throughout the application, you will run our queries through this function. This 
way, if the query fails for some reason, you will get a pretty good idea of what 
happened. This is another example of safe coding. After troubleshooting your code, 
you won't run into these problems often, but if a change is made somewhere 
(perhaps without your knowledge) you'll get a pretty good idea of what's going on. 



For a site that is publicly available, there is a danger in running every query 
through this function. If a query fails, a hacker is likely to see more about 
your setup than you'd like.To prevent this from happening you could define 
a constant (discussed shortly) that prevents the function from printing out 
descriptive errors. Something like this: 
function safe_query ($query = "") 
t 

if (empty ( Iquery) ) ( return FALSE; ) 

if(QUERY_DEBUG == "Off") 
{ 

Iresult = mysql_query ( $query ) or 




204 Part III: Simple Applications 



die ("Query failed: please 
conatact the Webmaster"; 



e I se 



iresult = mysql_query ( Squery ) 

or die("ack! query failed: " 

."<li>errorno=".mysql_errno() 
. "<1 i >error=" .mysql_error( ) 
. "<1 i >query=" . $query 
); 
• 
return $result; 



FROM /GUESTBOOK2K/HEADER.PHP 

Once again, this file will be included in every page in this application. It will keep 
all of the functions specific to this application. In addition, there are a few details 
that the first few lines of this application will see to. Notice the use of the variable 
$document_root. This is an Apache variable, accessible through PHP, which indi- 
cates the default root folder. By making use of this variable, our entire application 
becomes portable. If we move the entire book folder and all of its sub-folders, these 
files will be found and accessed properly. Keep in mind that this is an Apache vari- 
able; your operating system and Web server may require a different variable. Check 
phpinfo( ) to make sure. 

include("$DOCUMENT_ROOT/book/functions/charset.php") ; 

include("$DOCUMENT_ROOT/book/functions/basic.php") ; 

$conn = mysql_connect( "1 ocal host" , "username" , "password" ) or 

die("could not connect to database"); 
mysql_sel ect_db( "guestbook2k", $conn) 

die("could not select guestbook2k" ) ; 



define("PAGE_LIMIT", 2); 

Thefirst line includes our default character set. The charset.php file containsjust 
one line: 

header( "Content-Type : text/html; charset=IS0-8859-l" ) ; 



Chapter 8: Guestbook 2000, the (Semi-)Bulletproof Guestbook 



205 



This function will help prevent people from sending you values encoded in a dif- 
ferent character set. If they did send text in a different character set, the functions 
in cieanup_text( ) would fail, and you would still be open to some cross-site 
scripting hacks. This is a difficult problem. If you want more details check out these 
articles: 

http: //www. cert. org/tech_ti ps/mal i ci ous_code_mi t i gat i on. html 
http: //www. apache. org/ info/css-security/encodi ng_exampl es . html 

Here we've included something interesting: a constant, here named PAGE_UMIT. 
A constant is like a variable in that it contains a value (in this instance, 2). However, 
that value cannot be changed by a simple assignment or by functions other than 
define ( ). Constants do not run into the same scope problems that are encountered 
with variables, so they can be used within functions without having to pass them is 
arguments or worry about declaring globals. After running the define ( ) function, 
the constant PAGE_LIM IT will be available everywhere in my script. 

PAGE_LIM IT decides the number of entries that will be viewable on each page. 
You are welcome to change this if you would like to see a larger number. 




If you are putting together a query using a constant, you will have to end 
your quoted string in order to make use of the constant value. For example, 

query = "select * from db_name limit PAGE_LIMIT" 

will confuse MySQL, because PHP has not replaced the name of the constant 
with its value. However, this will work: 

query = "select * from db_name limit " . PAGE_LIMIT 




PHP has many built-in constants you can use within your scripts. A list of 
constants is included in the PHP manual: http://www.php.net/manual/ 
language.constants.php 



PRINT_ENTRY() This prints the results of a query within a table. 

function pri nt_entry ( $row,$preserve="") 
{ 

Snumargs = f unc_num_args ( ) ; 

for ($i = 2; $i < Snumargs; $i++) 



$field = f unc_get_arg( $i ) ; 
Sdbfield = str_repl ace( " " 



strtol ower($f iel d) ) ; 



206 Part III: Simple Applications 



$dbvalue = cl eanup_text( $row[$dbf i el d] , $preserve) ; 

$name = ucwords( $f i el d) ; 

print " < t r > \ n " ; 

print " <td valign=top al i gn=ri ght><b>$name : </b></td>\n' 

print " <td valign=top al i gn = l eft>$dbval ue</td>\n" ; 

print " </tr>\n\n"; 



The easiest way to see how this function works is to take a look at the line of 
code that calls a function. This snippet was taken from the view.php file: 

pri nt_entry ( $row, $p reserve , "name" , "1 oca ti on" , "emai 1 " , "URL" , "entry 
date" , "comments" ) ; 

Notice that the function itself has only two default arguments ($row and 
$preserve), while the call to the function has nine arguments. The first argument, 
$row, is a row from a database call. It is expecting that a row was taken from a 
query using mysql_fetch_array() so that the contents of row are an associative 
array, the keys of which are equal to the column names of the database table. The 
second argument, $preserve, is needed for the cieanup_text function, which we 
have discussed previously. The rest of the arguments are equivalent to associative 
keys in $row. 

The arguments sent to any user-defined function make up an array. The number 
of the final element in the array can be retrieved with func_num_args( ). Using the 
call to print_entry( ) seen above, the previous paragraph, func_num_args( ) 
would return 8. (There are 9 arguments, the first of which is 0.) 

The value of each argument can then be accessed with func_get_arg( ). This 
allows for a structure like the one used here, where a loop accesses and then 
processes each argument sent to the function. The first time through the for loop, 
$field is assigned the third element in the array, "name". You can use the value in 
$field to access an element in the associative array $row ($row["name"]). 

After you make sure the argument contains no capital letters or spaces, the value 
is sent to the cl eanup_text function and printed. 

It's nice to structure a function this way because it allows an arbitrary number of 
arguments to be sent to the function. You could include one or many fields to print. 

PRINT_INPUT_FIELDS() This function works much like print_entry( ). func_ 
get_args() makes $field an array, each element of which is an argument sent to 
the function. The list structure moves through all elements in the array and prints a 
text field for each. The name of the field will be in one table cell, and the input box 
will be in an adjoining cell. 

function p r i n t_i n p u t_f i e 1 d s ( ) 
{ 



Chapter 8: Guestbook 2000, the (Semi- )Bulletproof Guestbook 207 

$fields =f unc_get_args( ) ; 

while (1 i st($i ndex, $f i el d) = each( $f i el ds ) ) 

t 

print " < t r > \ n " ; 
print " <td valign=top 
align=right><b>".ucfirst($field).":</b></td>\n"; 

print " <td valign=top al i gn=l eftXi nput type=text 
name=$field size=40 val ue=\" " . $GL0BALS[ "1 ast_$f iel d"] . "\"></td>\n" ; 
print " </tr>\n\n"; 



Notice the use of a global variable for the default value of the text field. This is 
here in the event that the user enters bad information and the information needs to 
be re-presented with the values he or she entered. Why would information need to 
be printed a second time? That should make perfect sense after you read about the 

next function, create_entry( ). 

CREATE_ ENTRY We are not going to simply dump user information into the data- 
base. First it needs to be verified. 

function create_entry ( $name , $1 ocati on , $emai 1 ,$url ,$comments) 
t 

// remove all HTML tags, and escape any 

//other special characters 

$name = cl eanup_text( $name) ; 

$location = cl eanup_text( $1 ocati on ) ; 

$email = cl eanup_text( $emai 1 ) ; 

$url = cl eanup_text( $url ) ; 

$comments = cl eanup_text( $comments ) ; 

// start out with an empty 

//error message, as validation tests fail, 

// add errors to it. 

$errmsg = " " ; 

if (empty ( $name) ) 

{ 

$errmsg .= "<li>you have to put in a name, at least!\n"; 



// do a very simple check on the format of the email address 
// supplied by the user, an email address is required, 
if (empty($email ) || ! eregi ( " A [A-Za-zO-9\_-]+@[A-Za-zO-9\_ 
-]+. [A- Za-z0-9\_- ]+.*", $email )) 



208 Part III: Simple Applications 

$errmsg .= "<li>$email doesn't look like a valid email 
address\n" ; 
) 

el se 
{ 

// if the format is OK, check to see if this user has already 
// signed the guestbook. multiple entries are not allowed. 

$query = "select * from guestbook where email = '$email'"; 

Iresult = safe_query ( $query ) ; 

if (mysql_num_rows ( $resul t ) > 0) 

{ 

$errmsg .= 

"<li>$email has already signed this guestbook. \n" ; 

) 
} 

// perform a very simple check on the format of the url supplied 
// by the user (if any) 

if ( !empty($url ) && leregi ( " A http: //[A-Za-zO-9U\?\_\ : \~\/\ . 
-]+$", $url )) 
{ 

$errmsg .= "<li>$url doesn't look like a valid URLAn"; 



if (empty ( $errmsg) ) 

{ 

Iquery = "insert into guestbook " 

." (name , 1 ocati on , emai 1 , url , comments , remote_addr) values " 
."('$name', '$location', '$email', '$url', 
'$comments' , ' $REM0TE_ADDR' )" 

safe_query ( $ query) ; 

print "<h2>Thanks, $name ! ! </h2>\n" ; 
) 

el se 
{ 

print <<<E0Q 

<P> 

<font color=red> 

<b> 

<ul> 

$errmsg 

</ul> 

Please try again 



Chapter 8: Guestbook 2000, the (Semi- )Bulletproof Guestbook 209 



</p> 

EOQ; 



return $errmsg; 



This function is going to make sure that the information entered is moderately 
useful. If there are problems with the information, a text string describing the prob- 
lem will be assigned to the variable $errmsg. If, after the function is executed, 
$errmsg is empty, the values will be inserted into the database. Otherwise the error 
message will be printed, and the values the user entered will be assigned to globals so 
that they can be printed as the default values in the text fields the next time through. 

In order, this function checks for the following: 

♦ That the name field contains something 

♦ That the e-mail address is potentially a proper address (contains text, an 
@, and a period (.)) Note that this is not very strong validation of e-mail. 
It takes a very long and complicated script to thoroughly validate an 
email, as you will see in later chapters. 

♦ If the e-mail looks okay, that this e-mail address hasn't been entered in 
the database already 



♦ That the URL is potentially valid 




Check Appendix F for more detail on regular expressions. 



SELECT_ENTRIES() This function's sole purpose is to put together your database call. 

function sel ect_entri es ($offset=0) 
t 

if (empty($offset)) ( $offset = 0; ) 

$query = "select * 

, date_format(created, '%& %M, %Y %h:%i %p') as entry_date 

from guestbook 

order by created desc 

limit $offset, " . PAGE_LIMIT 

$result = safe_query ( $query ) ; 

return Iresult; 



210 Part III: Simple Applications 

You already know that page_limit sets the number of records displayed per 
page. As the second argument in the limit clause, the $offset variable indicates 
which records will be returned from the query. If you are having problems under- 
standing $of f set, take a look at the explanation of the limit clause in Chapter 3. A 
value for $off set will be passed through the navigational elements. We'll examine 
this in detail when we discuss the next function. 

To retrieve the date value in a readable way, this query makes use of MySQL's 
date functions. MySQL stores the date and time as a 14-digit number (YYYY:MM: 
DD:HH:SS), but it's nicer to return the date information in a way that's easier for 
humans to read. The MySQL date_format function retrieves the information in the 
way we want to use it. This function and many other MySQL functions are dis- 
cussed in Appendix I. 

NAV() This function's sole purpose is to create navigational elements. 

function nav ( $of fset=0 , $thi s_scni pt=" " ) 
{ 

global $PHP_SELF; 

if (empty($this_script) ) ( $this_script = $PHP_SELF; ) 
if (empty($offset)) ( $offset = 0; } 

// get the total number of entries in the guest book - 

// we need this to know if we can go forward from where we are 

Iresult = safe_query ( "sel ect count(*) from guestbook"); 

1 i st( $total_rows ) = mysql_fetch_array ( $resul t) ; 

print " < p > \ n " ; 

if (loffset > 0) 

{ 

// if we're not on the first record, we can always go 
backwards 

print "<a href=\"$thi s_scri pt?off set=" . ( $off set-PAGE_ 
LIMIT) . "\"><<Previous Entries</a>   "; 

) 

if ($offset+PAGE_LIMIT < $total_rows) 

{ 

// offset + limit gives us the maximum record number 
// that we could have displayed on this page, if it's 
// less than the total number of entries, that means 
// there are more entries to see, and we can go forward 
print " < a 
href=\"$this_script?offset=".($offset+PAGE_LIMIT)."\">Next 
Entries&gt ;&gt ;</a>   "; 



Chapter 8: Guestbook 2000, the (Semi- )Bulletproof Guestbook 211 



print " < / p > \ n " ; 



When appropriate, this function will insert links that will enable the user to view 
the next set of entries, the previous entries, or both. It is all determined by the 
$of f set variable and the PAGELIMIT constant. 

The first time through there will be no valuefor $offset, and therefore there will be 
no previous entries link (because $offset will not be greater than 0). But if there are 
more rows to be displayed, a link will be created that creates a value for $of f set to 
be accessed if that link is followed. 

Say it's the first time we're executing this function, so $ offset has no value, and 
there are 10 rows in the database. When it reaches the last if..., the script will see 
that there are more rows to be displayed ($of f set + page_limit = 2, which is less 
than 10), and so the following link will be printed. 

<a href="/book/guestbook2k/view.php?offset=2"> 
Next Entries&gt ;&gt ;</a> 



Interesting code flow 



Once you understand how the functions presented thus far work, you should have 
no problem figuring out how guestbook2k works. For the most part, very, very little 
work is done in the pages called by the browser. These pages are pretty much an 
assemblage of function calls. 

We will break down one file in detail so you can get the feel of how this structure 
works. Most of the rest you should be able to figure out by flipping between the files 
and the explanations of the functions. In the following sections we will walk 
through the view.php file. 

VIEWING ENTRIES 

Here is the logical flow of the code. 

<?php 

include " header. php"; 

$page_title = "View My Guest Book!!"; 

include "start_page . php" ; 

?> 

The first thing you need to do in every page is include the header.php file. This 
will allow access to all of the functions we outlined previously. After that you 
should include standard header information from start_page.php. You have to 
declare $page_ title prior to including start_page.php, so that the title can be printed 
in a standard way across every page. 



212 Part III: Simple Applications 

<table border=0> 

<?php 

if (empty($offset)) ( $offset = 0; } 

Iresult = sel ect_entri es( $off set ) ; 

Ipreserve = ""; 

while ($row = mysql_fetch_array ( Iresul t ) ) 

{ 

print_entry($row,$preserve, "name" , "1 oca t ion" , "email " , "URL" , "entry 

date" , "comments" ) ; 

print "<tr><td col span=2>&nbsp ;</td></tr>\n" ; 
} 

mysql_free_result($result) ; 
?> 

</table> 

<?php 

nav( $of f set ) ; 

include "end_page. php" ; 
?> 

This is it. You determine a value for $off set, run the query with the sel ect_ 
entriesO function, and then print the results by running the print_entry( ) 
function within a while loop. Navigational elements are determined by the nav( ) 
function. 

DELETING ENTRIES 

The most complex portion of this application involves deleting entries from the 
guestbook. This stands to reason because you don't want your guestbook being 
fooled by anonymous users. So the first thing you need to do before deleting 
an entry is authenticate users. When discussing the authenticate( ) function, we 
showed how an HTTP 401 header will bring up the browser's username and 
password dialog box. The values entered then need to be checked against the 
guestbook_admin database table. The authenticate.php file takes care of this for 
you, which is why this file is included in the edit.php file. 
The heart of authenticate.php is this: 

if (empty($PHP_AUTH_USER)) 
{ 

authenticate($realm,$errmsg," header"); 
} 



Chapter 8: Guestbook 2000, the (Semi- )Bulletproof Guestbook 213 

el se 
t 

$query = "select username from guestbook_admi n where password = 
passwordd ower( ' $PHP_AUTH_PW ) ) and username = 
lower( '$PHP_AUTH_USER' )"; 

$result = mysql_query ( $query ) ; 

if ($result) ( 1 ist($val id_user) = mysql_fetch_row($resul t) ; ) 

if (!$result || empty ( $val i d_user) ) 

{ 

authenticate($realm,$errmsg, "query"); 



print "<p><b>Editing as $PHP_AUTH_USER</bX/p>\n" ; 

If no username has been entered the header is sent through your authenticate( ) 
function. If the username does exist, a query is sent to the database to validate the 
user. If a row is returned, the user is validated and can continue working; otherwise 
the header is sent again. 

Once a valid username and password have been entered, the remainder of the 
edit.php file will be sent. But this time, in addition to all the other information, the 
checkbox will be included, so the user can decide which entries should be deleted. 
The value of the checkbox will be the primary key of the guestbook table. 

while ($row = mysql_fetch_array ( $resul t ) ) 
{ 

pri nt_entry ( $ row, $preserve , "name" , "entry 
date" , "1 ocati on" , "emai 1 " , "URL" , "comments" ) ; 

print " < t r > \ n " ; 

print " <td valign=top al i gn = ri ght><b>Del ete?</b></td>\n" ; 

print " <td valign=top al i gn=l eftXi nput type=checkbox 
name=\"entry_i d[]\" val ue=\" " . $row["entry_i d" ] . "\"> Yes, delete 
entry #" . $row[ "entry_i d"] . "</td>\n" ; 

print " </tr>\n\n"; 

print "<tr><td col span = 2>&nbsp ; </tdX/tr>\n" ; 



This form is then submitted to the confirm_delete.php file. Notice how you're 
passing an array here. The name of the form element is entry_id[], which means 
that when this form is passed to PHP, entryjd will become an array. The number of 
values in the array depends on the number of boxes checked. HTTP will not send 
the unchecked boxes at all. 



214 Part III: Simple Applications 

The first time through the confirm_delete.php file, we will print out the entries. 
This will make the person deleting these entries make sure he or she isn't doing 
something stupid. 

while ( 1 i st( $key , $val ue) = each( $entry_i d) ) 
{ 

print "<li>Delete entry #$value?\n"; 

print "<input type=hidden name=\"entry_i d[]\" 
val ue=\"$val ue\">\n" ; 



If any of these entries are to be deleted, this page will submit to itself, with a 
different value (Confirm Delete) sent with the submit button. This will make the 
following code run: 

while ( 1 i st( $key , $val ue) = each( $entry_i d) ) 
{ 

print "<1 i >Del eti ng entry #$value\n"; 

$q = "delete from guestbook where entry_id = $value"; 

safe_query ( $q) ; 
} 

We loop through the $entry_id array, deleting records for each member. 



Scripts 



There are a few more scripts, but these don't warrant much discussion, sign.php, 
start_page.php, end_page.php, and confirm_delete.php, are included on the CD. We 
suggest you look at them and the comments to get a feel for how they fit into the 
application. 



Summary 



The skills you learned here may not get you the big bucks as a programmer, but 
there if you understand everything that is being done here, you should be in pretty 
good shape as you move forward in your PHP programming life. 

In particular, you should see the priority that is put on creating reusable code. 
Nearly everything we have is in functions. This makes it much more likely that the 
code we write will be usable in some future application. 

Additionally, you got to see some basic validation. Validation is an important 
concept and one you will need to take very seriously when your application allows 
for user input. If you'd like to see how seriously some people take validation, check 
out Manual Lemos' form validation class, which is included on the CD. 



Chapter 9 

Survey 

IN THIS CHAPTER 

♦ Learning functions for creating HTM L tags 

♦ Understanding data that use a relational structure 

♦ Putting MySQL's date functions to work 



If a guestbook is the most common application on the Web, a survey isn't far 
behind. Many sites have some sort of widget that lets you choose you favorite color 
or sports hero, or whatever, to see what percentage of voters take what stance. So 
let's go forth and create a survey. 

In this application there will be a bit more complexity than you saw in Chapter 8. 
The programming will get a bit trickier, and the administration of the application 
will require more work. Unlike the guestbook, this application will require some 
knowledge of database theory. There are related tables, complete with the primary 
and foreign keys discussed in Section 1 of this book. This means that your SQL 
queries will include joins. 

Determining the Scope and 
Goals of the Application 

A survey application could be ultra- simple. If you wanted only to gather responses 
from a single question and return basic statistical information on the responses 
(how many votes for choice A, B, and so on), you wouldn't need a whole lot of code 
(or a chapter explaining it). A single table that stored answers would do the trick. 
The question could even be hard-coded into the HTM L. But that would not make for 
very interesting learning experience, would it? 

It gets more interesting if there can be any number of questions. Instead of just 
one, this application will allow for two, five, ten, or more— whatever you want. Not 
only that, this survey will record demographic information (such as age and coun- 
try of origin) and allow for sorting on the basis of this information. We also decided 
to add the ability to pick a winner from those who filled out the personal informa- 
tion —this might encourage people to give real rather than fictitious answers. 

There is one more wrinkle to discuss here. There is really no way to create a sur- 
vey application that records perfect data. Even if you go to extreme lengths, there 



215 



216 



Part III: Simple Applications 



will always be an opportunity for the shrewd and persistent to submit multiple 
entries. But in all likelihood your survey will not have to pass muster with the 
Federal Elections Commission. A small step to weed out those ruining your survey 
should do the trick, and you will see one way to accomplish this later on. 



Necessary Pages 



Entering and viewing information will require three pages. The first is where the 
questions will be presented and where the user will enter name, address, and geo- 
graphic and demographic information. A second page will show the basic survey 
results. A third will give a detailed breakdown. Figures 9-1, 9-2, and 9-3 show these 
pages respectively. 



J File Edit View Favorites lools Help 



Address | http:/V1 92.1 68.1 .1 /book/survey/tndex.php 




User Survey 

Thanks for filling out the survey. Be sure to fill out all the questions, and become eligible for our weekly prize 
drawings! Please, only one entry per person. 

See Current Results 

Questions About Life 

Which is your favorite state?: 



Questions About You 



Name: [ 

Email: j 

Age: |~ 

Country: [ 



"3 



m start | m%9®m%B& 



j=J 



¥?0>M -dBv'^-S 11:58 AM 



Figure 9-1: Page for filling out survey 



Note that for this application you will use a .gif file for your chart. The script will 
change the size attributes of the gif in the <img> tag to give a representation of the 
information. This works, but isn't necessarily ideal. You could install the gd 
libraries and compile PHP with the -with-gd flag. These functions are beyond the 
scope of this book. 

This application, like all others, needs some administrative tools. For starters, 
you will need to be able to add, delete, and edit questions. Additionally, there is a 
page that selects a winner at random from the database. Figures 9-4 and 9-5 show 
the administrative page and the select winner page, respectively. 



Chapter 9: Survey 



217 



-3 Survey Results - Microsoft Internet Explorer 



Mi 



J File Edit View Favorites lools Help 



Address | j|] http:/AI 92. 1 68. 1 . 1 /book/surveyMesults. php 



73 ^Go 



Survey Results 

Which is your favorite state? 

vt 60% ^^^ 

wv 20% ^M 

117 20% ^™ 

What is your favorite county? 

San Francisco 100% ^ 



aastarti m«ii*>®m%B& |]jg||a_a|;tj %^^om *-- &&<<*>% 12:03 pm 



Figure 9- 2: Basic survey results 



y Complex Survey Results - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



Address 1 8] http://1 92. 1 68. 1 . 1 /book/survey//compleiwesults?questionJd=2 



Mi 



~2J &Go 



Which is your favorite state? (5 total votes) 
By age: 



By country: 



By state: 



|Answer |didn't say |under 16 17 to 32 |33 to 48 |49 to 64 |65 and up 


|vt 33.3% | 33.3% | 33.3% | 


|wv 100% | 


|ny 100% | 



[wv 



|Answer 


| Canada 




Other 


|USA 


|USA 




\^~ 


1 






33.3% | 


1 


66.6% 


\WY 


1 






1 


100% | 




|ny 


1 


100% 




1 


1 








J 



zi 



State 



CA 



66.6% 



33.3% 



didn't say 



;astart| m €j *> & m j) 'A j^^jjij mM^VO'U yftft^^ 



12:05 PM 



Figure 9- 3: Detailed survey results 



218 



Part III: Simple Applications 



3 Survey Admin: Edit Questions 


Microsoft Internet Explorer 




HEJB 


J File Edit View Favorites 


lo 


ols Help 






Q 


Address |£] http7AI 92.168.1. 1 /bookVsurvey/admin_quesr. 


ons.php 




Z\ ^Go 1 


Current Questions 












I i 

Which is vour favorite state? 
Add A Question 


Question: | 








Answers: 








I 






1 


I 






1 


I 






1 


I 






1 


I 






1 


I 








I 






1 


I 








iggsiartjlj ft v \m m & 'A 


wmw\ 


etiJ||WAt« 1 5 1 #%*ytft 


<*>% 


12:07 PM 



Figure 9-4: Survey administration page 



3 Survey Admin: Weekly Random Drawing - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



*• - *-@©ai®a0| 



Address | http://192.1G8.1.ll'book/survev/admin_get_winner.php?weekdate=2000-05-28 



~ZJ <^ G ° 



Editing as jay 

Survey Admin: Weekly Random Drawing 

Draw a winner for the week of 2000-05-28 

fred programmer fred@fred.com WINNER! Notify Winner 

Past Weeks: 

2000-05-28 
2000-06-04 
2000-07-02 
2000-10-29 
2000-11-05 

Admin Page 
;gastart| <S> & m ^ ^j ft, ftj jgj Hj 



^'ff'K^./lfflOt'S* 



^ 

7:47 AM | 



Figure 9-5: Select winner page 



Chapter 9: Survey 



219 



Winners will be notified via e-mail and sent a URL that they will need to travel to 
in order to claim their prize. This page will look like the one in Figure 9-6. Once there 
they will need to confirm who they are, just so you have an extra level of security. 



'a Claim Your Prize In Our Weekly Drawing - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



MS 

D 

f>Go 



Address |@ rittp7/madfisri.com:808uyt.ook/survey/claim.prip 



"3 &G° 



~3 



Claim Your Prize In Our Weekly Drawing 

Welcome! To claim your prize, please enter the email address where you receive d your notification mess age, and 
then click on the button that says "I accept!" 



Your Email Address: | 
I accept! I 



;ast 3 rt| ^ <Sj q> & ■ J; £j |jjjjJj^||llte>» 1 ffi^%<$Mr|gt'0=B 12:13 PM 

Figure 9- 6: Claim prize page 



What do we need to prevent? 



In the previous chapter we discussed methods for removing junk information that 
people attempt to send through the form elements. We will continue to use these 
functions here. This application will also do some e-mail address validation. 




Want to see what it really takes to verify that an email is in the proper for- 
mat? It takes a lot of work. Take a look at the CheckEmail.php file in /func- 
tions directory on the CD. You can see that it takes multiple regular 
expressions to make sure the e-mail is just right. Given that regular expres- 
sions are fairly slow, you may be wondering if it is even worth running a 
script like that, especially if you are running a site with very heavy traffic.You 
will need to decide that for yourself. Do you need to make sure e-mails are 
perfect, or will a simpler, less-robust form of validation be good enough? 
Even if you make sure the address is in the proper format, there's almost no 
way to know if the address is attached to an actual mailbox. 



220 Part III: Simple Applications 

This application will provide you with a simple means for blocking some people 
from entering information at your site. It's nothing terribly sophisticated; a savvy 
Internet user would be able to work around it in a minute. Using the form shown in 
Figure 9-6 you will be able to enter a domain of origin that will be blocked from 
the site. All users who enter data will have their remotejost variable checked 
against a table in the database. If that host is found, the application will refuse that 
user access. Again, this isn't perfect. Depending on the ISP used, some clients won't 
even identify the remote_host in the HTTP header. If you really have sensitive 
information and need effective means of blocking users, you should work with 
some sort of login scheme. This is just an example of what you could do with a 
database and HTTP header information. 

You'll also need to take some steps to make sure that the wrong people won't be 
claiming prizes. You'll need to make sure that the people coming to claim prizes are 
who they say they are. 

Designing the Database 

This survey application allows for any number of questions. Each question can 
have any number of answers. To create this relationship you'll need two tables, one 
named questions and one named responses, that have a one- to-many relationship. 
(Each (1) question can have (n) any number of answers.) 

User information is best represented with multiple tables as well. A table named 
users will store the relevant user information. Two tables, named states and coun- 
tries, serve as lookup tables and have one-to-many relationships with the users 
table. 

Finally, there are two tables with relationships to no others. They store other 
information this application needs to track. They are aptly named blocked_domains 
and survey_admin. 

Figure 9-7 shows a visual representation of the structure of the database. The 
create statements for making these tables are shown in Listing 9-1. Note that these 
table definitions were copied from the mysqi dump utility. If you're not aware of 
mysqi dump, or the other mysqi utilities, make sure to check up on Appendix C. 



Chapter 9: Survey 



221 



questions 



answers 



responses 




answerjd 
questionjd 
answer text, 



answerid 

questionid 

answer 



users 



countries 



Figure 9- 7: Survey database schema 

Listing 9- 1: Create Statements for Survey 

# Table structure for table 'age_ranges' 
# 

CREATE TABLE age_ranges ( 

mir_age int(ll) NOT NULL, 

max_age int(ll) NOT NULL, 

age_rarge varchar(lO) 
); 



# 

# Table structure for table 'arswers' 



blocked domain 


domain 




blocked 


by 


blocked 


dt 


released 


dt 


notes 




modify_ 


dt 



Winners 



userid 

name 

email 

country 

state 






countries 




userid 
weekdate 
notify_dt 
claim dt 




states 












statename 
state 














i — >~ 




survery adm 



username 
password 



CREATE TABLE arswers ( 

answer_id int(ll) NOT NULL auto_i ncremert , 

questi or_id i nt( 11 ) , 

answer text, 

PRIMARY KEY (answer_id) 
); 



222 Part III: Simple Applications 

# 

# Table structure for table ' bl ocked_domai ns ' 
# 

CREATE TABLE bl ocked_domai ns ( 

domain varcharOO) NOT NULL, 

block_by varchar(lO) NOT NULL, 

block_dt datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, 

release_dt datetime, 

notes text, 

modify_dt timestamp( 14) 
); 



# 

# Table structure for table 'countries' 
# 

CREATE TABLE countries ( 

country varcharOO) NOT NULL 



# 

# 

# Table structure for table 'questions' 
# 

CREATE TABLE questions ( 

questioned int(ll) NOT NULL auto_i ncrement , 

question text NOT NULL, 

KEY question_key (questi on_i d) 



# 

# Table structure for table 'responses' 
# 

CREATE TABLE responses ( 

user_id int(ll) NOT NULL, 

answer_id int(ll) NOT NULL 
); 



Chapter 9: Survey 



223 



Table structure for table 'states' 



CREATE TABLE states ( 

statename varcharOO) NOT NULL, 

state char(2) NOT NULL 
); 



# 

# Table structure for table 



' survey_admin ' 



CREATE TABLE survey_admi n ( 

username varchar(50) NOT NULL, 
password varchar(255) NOT NULL 

); 



# 

# Table structure for table 



users 



CREATE TABLE users ( 

user_id int(ll) NOT NULL auto_i rcrement , 

name varchar(50) , 

emai 1 varchar(50) , 

country varchar( 20) , 

state char(2) , 

age i n t ( 1 1 ) , 

remote_addr varchar(15), 

remote_host varchar(80), 

create_dt timestamp( 14) , 

KEY user_key (user_id) 
); 



# 

# Table structure for table 'winners' 



224 



Part III: Simple Applications 



CREATE TABLE winners ( 

user_id int(ll) NOT NULL, 

weekdate datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, 

notify_dt datetime, 

claim_dt datetime, 

conTirm_dt datetime 
); 



Code Overview 

If you already read the section by the same name in Chapter 8, the structure we use 
here should be familiar to you. Items in the /functions folder are included and ready 
for reuse. 

It's obvious that this survey application requires several more pages than the guest- 
book: there's more that needs to be done. Though you can include several actions in a 
single page, and sort through the ones you need by passing variables and using if state- 
ments, it can make code difficult to keep track of. Better to have several intuitively 
named files that perform specific tasks. That said, there are pages in this application 
that make use of variables in order to decide what action to take on a given page. 

If you've done any Web work at all you know how tedious it can be to deal with 
HTM L tables and forms. For that reason, in this and most of the other applications 
in this book, we will try to ease the pain involved in dealing with tables and forms. 
In the following sections you will see several functions that will make life, in the 
long run at least, a lot easier. The functions in the coming sections will make a lot 
more sense if you see what they accomplish first. Here's some code that will work 
just fine if used with the following functions. 

print start_tabl e( ) ; 
print table_row( 

tabl e_cel 1 ( "Cel 1 text") 
); 
print end_tabl e( ) ; 




Ifyou don't like the functions we've created fortables,forms,and other HTML 
elements, don't use them. It is perfectly acceptable (and perhaps even more 
common) to type out HTML elements, rather than create them through func- 
tions. Like many things in programming, it comes down to a matter of prefer- 
ence. In a case like this there's no right answer: Do what you prefer. 



This will create a table with one cell. You could build on the complexity of this 
by adding additional tab! e_cel l ( ) calls within the tabl e^row function call. You 



Chapter 9: Survey 225 



can do this because of PHP's ability to deal with a variable number of arguments. 
You can design the tabl e_row function to loop through all of the arguments (some 
of which are calls to the table_cell() function). You may be wondering how 
these functions deal with table attributes, like width, align, and others. How could 
you alter those for particular tables? These attributes and their values must be 
included in the function call, so the attributes must be another argument in the 
function call. The function will identify the attributes by variable type. That is, the 
function will check the variable type of the argument, and if it is an array (an asso- 
ciative array), the function will assume it contains attribute information, and will 
turn the key/value pairs from the array into name=vai ue attribute pairs in a string. 
A more complex function call would look like this: 

print (table_row (array ( "bgcol or" => $bgcolor), 
tabl e_cel 1 ( "New entry"), 

tabl e_cel 1 ( text_f i el d( "entered_by" , "", "10")), 
tabl e_cel 1 ( submi t_f iel d( "submi t" , "i nsert" ) ) 
) 
); 

Here the first argument, (array ("bgcolor"=> $bgcolor)), identifies the row's 
background color; the remaining arguments create table cells. As an added bonus, 
these table cells contain form elements. 

Keep in mind that the methods for achieving nested function calls will be 
explained later. The application will also be using more of MySQL. Throughout this 
application, there is more extensive use of MySQL functions than seen in Chapter 8. 



Code Breakdown 



As with the guestbook application, the code here is divided for convenience. The 
functions used exclusively by this application are in the /book/survery/header.php 
file. This will be included in every file. The start_page.php and end_page.php will 
contain header and footer information, respectively. 

Since we covered the functions sitting in the /book/functions/functions.php file 
in Chapter 8, we're not going to go over it again here. But we did add a little bonus 
this time around. 

Reusable functions 

As your applications get more complex, you're going to need to continually use 
some HTML ingredients— forms, tables, paragraph tags, anchors, and the like. For 
this reason we've added a series of functions that make it easier to create those 
repetitive HTM L elements. We've also moved some of the commonly used database 
functions into their own file. 



226 



Part III: Simple Applications 



FUNCTIONS FROM /BOOK/FUNCTIONS/DB.PHP 

Some of this will look familiar if you've gone through the guestbook application in 
Chapter 8. But know that for the rest of the applications, you can find these func- 
tions in this file. We're not going to cover every function, just those that require 
some explanation. 

SET RESULT VARIABLE!) This function turns results of a query into global vari- 
ables. 

function set_resul t_vari abl es ($result) 



if (!$result || !mysql_num_rows( Iresul t) ) ( return; ) 
$row = mysql_fetch_array($resul t ,MYSQL_ASSOC) ; 
f ( ! i s_array ( $row) ) 

print $query . "<1 i >no array returned : resul t=$resul t row=$row"; 
return $result; 

while (list($key,$value) = each(lrow)) 

global $$key; 
$$key = $val ue ; 



If you remember our discussion about variable scope in Chapter 7, you'll remem- 
ber that variables passed through either GET or POST or declared outside of a func- 
tion are globals, and they are available within the GLOBAL's array. 

Frequently, you are going to want to use the column names used in your query 
as variables. It's nice to be able to use them without having to go through the asso- 
ciative array returned by mysqi_fetch_array( ). This function will turn the vari- 
ables used in a query into globals throughout your script. 

Iquery = "select distinct fname, Iname from table_l where id=l"; 
Iresul t=safe_query ( $ query) ; 
set_resul t_vari ables($result) ; 

So this code will make the variables $f name and $i name, along with their values, 
available as globals. If a result set with multiple rows is sent to the query, only the 
first row is used by this function. 

Note the use of variable variables in this function. 



while (list($key,$value) = each(lrow)) 
{ 

global $$key; 



Chapter 9: Survey 227 

$$key = $val ue ; 
} 

Each element is taken from the array retrieved from mysql_fetch_array(). The 
key (the column name) is declared as a global variable. Then that global is assigned 
the contents of $value. Variable variables are discussed in Chapter 7. 

This function is intended to work with the fetch_record( ) function docu- 
mented next. 

FETCH_RECORD() This function helps create simple queries. 

function fetch_record (Stable, $key="", $value="") 
{ 

$query = "select * from Stable "; 
if (! empty ($ key) && ! empty ( $val ue) ) 
{ 

if ( i s_array ( $key ) && i s_array ( $val ue) ) 
t 

$query .= " where "; 

$ a n d = " " ; 

while (list($i,$v) = each($key)) 

{ 

$query . = "land $v = ".$value[$i]; 
$ a n d = " and"; 



e I se 



$query .= " where $key = lvalue "; 
1 
} 

Sresult = safe_query ( $query ) ; 
if ( !empty($key) && ! empty ( $val ue) && 
mysql_num_rows( Iresul t ) == 1) 
1 

set_resul t_vari ables($result) ; 
} 
return Iresult; 



It won't be of much use if you need to use MySQL's functions or if you need to 
use fancy predicates (LIKE, GROUP BY HAVING). It doesn't take a list of columns to 
be selected from the database, but it can take multiple parameters and values in the 

where clause. 



228 Part III: Simple Applications 

First, check whether one value or multiple values have been sent in the $key and 
lvalue variables. If scalar variables are sent, only one item will be added to the 
where clause. If arrays are sent, they will be looped through and added to the where 
clause. 

If there is only one row returned by the query, it calls the set_result_ 
variabies( ) function to set the columns and their values as global variables. In all 
cases, the function then returns the entire result identifier from the query. 

DB_VALUES_ARRAY() This function is extremely useful for creating drop-down 
boxes and other form elements from a database table. 

function db_val ues_array ($table="", $val ue_f i el d=" " , 
$label_field="" 

, $sort_f i el d=" " 

, $where="" 



$val ues = array ( ) ; 

if (empty ( $tabl e) || empty ( $val ue_fi el d) ) { return lvalues; 

if (empty($label_field)) ( $label_field = $val ue_f i el d ; ) 
if (empty($sort_field) ) { $sort_field = $1 abel_f iel d ; ) 
if (empty($where) ) ( $where = "1=1"; 1 

$ query = "select $value_field as value_field 
, $label_field as label _f i e 1 d 
from Stable 
where $where 
order by $sort_field 

Iresult = safe_query ( $query ) ; 

if (Iresult) 

{ 

while ( 1 i st( $val ue , $1 abel ) = mysql_fetch_array ( $resul t ) ' 

{ 

$val ues[$val ue] = $label; 

) 
} 
return lvalues; 



In this application, and many others that you'll come across, there will be tables 
in your database that will serve as "look up" tables. These are tables whose sole 
purpose is to ensure that good information ends up as other tables. Take a look at 



Chapter 9: Survey 229 



the diagram in Figure 9-6, and you will see that the countries and states tables 
serve this purpose. 

A query is carefully crafted. Aliases are set (using "as") so that the column names 
will match the attributes you wish to have in the forms. An associative array is then 
created; the $vai ue will serve as the key in this associative array and will probably 
become the value attribute in the HTM L form. 

FUNCTIONS FROM /BOOK/FUNCTIONS/HTMLPHP 

These functions make it easier to create common HTM L tags. M ost of the functions 
in this file are very similar. But before we get to these, you will need to understand 
the get_atti isto function, which has been added to the basic. php file. 

GET_ATTLIST() This function takes an associative array and creates name="value" 
pairs suitable for HTM L tags. 

function get_attlist ( $atts=" " , $def aul ts=" " ) 
t 

$1 ocal atts = array ( ) ; 

$attl 1st = "" ; 

if ( i s_array ( $defaul ts ) ) { llocalatts = Idefaults; ) 
if ( i s_array ( $atts ) ) ( llocalatts = array_merge( $1 ocal atts , 
$atts); } 

while (list($na me, lvalue) = each(llocalatts)) 
t 

if (lvalue == "") ( lattlist ,= "Iname "; } 

else ( lattlist .= "$name=\"$val ue\" "; ) 
} 
return lattlist; 



No matter the base tag, all HTML tags take attributes in the form name="vai ue". 
This function will build an attribute list for any tag. The function will be called by 
other functions that write out individual HTM L tags. As you can see, this function 
takes two arguments. The second is an array with a set of attributes required by a 
specific tag. For instance, an <img> tag isn't much good without an src attribute. 
So if the function is called from another function that creates images, the second 
argument should be an array with one element, something like imyarray = 

array ( "src" => "my image . gi f " ). 

The first argument will take another array containing other attributes. For the 
<img> tag, that first array might contain alt text, width, and height — imyarray = 

array("alt" =>"My Image", "width"=>"20" , "hei ght"=>"25". 



230 Part III: Simple Applications 

If appropriate, these two arrays will be merged into one. Then, from this merged 
array, a string is created that has the "name"= val ue pairs. If a value is empty, the 
name will exist without a value. 

Note that elements passed in the second array will overwrite those in the first, 
enabling you to overcome default values easily. This occurs because in the array_ 
merge( ) function, if there are two elements with the same associative key, the last 
one will overwrite the previous one. This allows other functions that create HTM L 
tags to keep a set of defaults in the first argument and values for the specific call in 
the second. 

Take a look at the following functions to get a better idea of how this works. 

ANCHOR_TAG() This function creates an anchor tag. 

f uncti on anchor_tag( $href=" " , $text=" " , $atts=" " ) 
{ 

lattlist = get_attl i st( $atts , array ( "href "=>$href) ) ; 

loutput = "<a $attl ist>$text</a>" ; 

return loutput; 
} 

For an anchor tag, there are only two things you could really expect every time: 
an href attribute and some text to go between the opening enclosing <a> tags. 
However, it is possible that a name attribute might be helpful. But more often than 
not, the call to this function will be something like this: 

anchor_tag( "myurl . com/i ndex. html " , "this is a great link"); 

Note that if there were a third argument, it would have to be in the form of an 
array. These arguments are then sent to the get_attlist() function and turned 
into a usable string, which is put together in the loutput variable. 

IMAGE_TAG() This creates an <img>tag 

f uncti on image_tag( $src=" " , $atts = " " ) 
{ 

lattlist = get_attl i st( $atts , array ( "src" = >$src) ) ; 

loutput = "<img $attlist>"; 

return loutput; 



This function works just like the anchor_tag( ) function described above. 

PARAGRAPHO This function will print out opening and closing <p> tags and 
everything between them. 



Chapter 9: Survey 



231 



function paragraph ( $ a 1 1 s = " " ) 
t 

$output = "<p"; 

$i =0; 

$attlist = get_attl ist(latts) ; 

if ($attlist > "") 

{ 

$output .= " $attl i st" ; 

$ i ++ ; 
} 

$output . = ">\n"; 
$args = f unc_num_args ( ) ; 
while ( $ i < $args) 
t 

$x = f unc_get_arg( $i ) ; 

$output . = $ x . " \ n " ; 

$ i ++ ; 
} 

$output .= "</p>\n"; 
return $output; 



The first thing to understand about this function is that it will print not only the 
opening <p> tag along with its attributes, it will also print the closing </p> tag and 
everything that could occur between the two. This could include anchor tags, image 
tags, or just about anything else. The following function call would work just fine, 
and in fact is used within the survey application: 

print paragraph(anchor_tag( "admi n_bl ock. php" , 
"Return to Domain List")); 

There is one argument in this function call, which is another function call with 
two arguments. In effect, when one function call is nested inside another, PHP exe- 
cutes the internal onefirst. So first the anchor_tag( ) function is called and creates 
a string like <a href="admin_block.php">. Then the outer function is executed, so 
the call to the paragraph function will actually be something like this: 

print paragraph("<a href=\"adminJolock.php\">Return to Domain List</a>"); 

Note how flexible this becomes. By looping through the number of arguments, 
you can send any number of additional function calls to the paragraph function. 
And you can happily mix text and function calls together. In the while... loop, $x 
can be set to a text string or the output of the function call. So the following is a 
perfectly fine call to the paragraph function: 



print paragraph^ 

"<b>Blocked by:</b> $block_by <br>" 



232 Part III: Simple Applications 

, "<b>Date bl ocked: </b> lblock_dt <br>" 

, "<b>Date rel eased : </b> $release_dt <br>" 

, "<b>Last Modified:</b> $modify_dt <br>" 

, hi dden_f i el d("ol d_domai n" , Idomai n ) 
); 

UL_LIST() This function turns an array or a string into an unordered HTML list. 

function ul_list dvalues = "") 
{ 

loutput . = "<ul>\n"; 
if ( i s_anray ( $val ues ) ) 
{ 

while ( 1 i st( , $val ue) = each( $val ues ) ) 
{ 

loutput .= " <1 i > $ v a 1 ue</l i>\n" ; 
) 
) 

el se 
{ 

loutput . = " <li>$values</li>\n"; 
} 

loutput . = "</ul>\n"; 
netunn loutput; 
) 

With this function you can create a bulleted list. Most frequently, an array will be 
passed to the function, each member of which will be prepended with a <i i> tag. 
The function also prepends a string with <i i> if the contents of $values is a string. 

FUNCTIONS FROM /BOOK/FUNCTIONS/FORMS.PHP 

Most of these functions are fairly straightforward and don't require any explana- 
tion. We will show a couple just for examples. 

TEXT_FIELD() This prints out an HTML text field. 

function text_field ($name="", lvalue="", lsize=10, lmaxlen="") 
{ 

loutput = <<<E0Q 
<input type=text name="lname" val ue="$val ue" size="lsize" 
maxlength="lmaxlen"> 
EOQ; 

netunn loutput; 
} 



Chapter 9: Survey 233 



All the expected attributes should be passed to the function. Most of the other 
functions look similar to this one, the only real exceptions being the checkbox and 
radio button. 

CHECKBOX, FIELD!) This function creates an HTM L checkbox. 

function checkbox_f iel d ($name="", $value="", $label="", $match="") 
t 

$checked = ($value == $match || $label == $match) ? "checked" : 

$output = <<<E0Q 
<nobr><input type=checkbox name="$name" val ue="$val ue" $checked> 
$1 abel </nobr> 
EOQ; 

return loutput; 
} 

The only thing that may be of interest in this function is how you should note in 
your function call if a checkbox is to be checked by default. You do this by adding 
an argument called $match. If $match equals either $label or $value it will be 
checked by default. The radio_fieid function works in the same way. 

FUNCTIONS FROM /BOOK/FUNCTIONS/TABLES.PHP 

Until style sheets are a reliable means for positioning pieces of a Web page, it is 
likely that you'll be using tables extensively. These functions make creating tables 
easier. 

START_TABLE() This function creates an opening <table> tag. 

function start_table ($atts="") 
{ 

$attlist = get_attl ist(latts) ; 

$output = <<<E0Q 

<P> 

<table $attlist> 

EOQ; 

return loutput; 



Again, this function calls the get_atti ist( ) function to add attributes to the 
opening table tag. Add an array of attributes to the function call to have attributes 
added to the resulting tag. 

END_TABLE() This function creates the closing </tabl e> tag. 



234 Part III: Simple Applications 

function end_table () 
{ 

loutput = <<<E0Q 
</table> 
</p> 
EOQ; 

netunn loutput; 
) 

This function does pretty much what you would expect. It is not really necessary, 
but it supplies nice symmetry with the start_table( ) function. 

TABLE_ROW() This function not only prints out the opening <tr> tag and its 
attributes; it also prints the table cells that will be nested within the <tr> tags. 

function table_row () 
{ 

$attl i st = "" ; 

$ c e 1 1 s t r i n g = " " ; 

$ eel Is = f unc_get_args ( ) ; 

while (1 ist( ,$cel 1 ) = each( $cel 1 s ) ) 

{ 

if ( i s_array ( $cel 1 ) ) 
{ 

lattlist .= get_attl ist($cel 1 ) ; 
) 

el se 
{ 

if ( !eregi("<td",$cell )) 
{ 

$cell = table_cell ($cell ); 
} 
$ c e 1 1 s t r i n g . = " " . t r i m ( $ c e 1 1 ) . " \ n " ; 



loutput = <<<E0Q 
<tr $attlist> 
Icellstring 

</tr> 
EOQ; 

return loutput; 



This function works by taking nested function calls. 



Chapter 9: Survey 235 



print table_row( 

tabl e_cel 1 ( "hel 1 o world", array ( "al i gn" = >" ri ght" ) ) 
); 

The tabie_row() call in the preceding code has one argument, which is itself 
another function call. Since all the arguments sent to a function can be extracted 
using the func_get_args( ) command, you can set an array that contains all of the 
arguments. In the preceding code, that array is $cells. 

Remember when we said earlier that inner function calls are executed prior to 
outer ones? In the preceding example the tabl e_cel l call is executed first. Thus, 
the call to table row will actually be something like: 

print tabl e_row( "<td al i gn="ri ght">hel 1 o world</td>") 

So when this function call becomes the active argument, the regular expression 
will test false and the tabie_ceii function will not be called. This makes sense: 
Since you already have a table cell string, there is no need to call the tabl e_cei i 
function. 

You may be wondering why the function contains that regular expression and 
the tabl e_cei i ( ) function call. That's for a case like the function call in the fol- 
lowing code, where table cell isn't called explicitly. 

print tabl e_row( "Cel 1 text"); 

FUNCTIONS AND CODE FROM /BOOK/SURVEY/HEADER.PHP 

This file is included in every file in the survey application, so every one of these 
functions will be available. 

START OF PAGE Before we get to the functions in this file, there is some code that 
does a bit of housekeeping. 

if (!defined("LOADED_HEADER")) 
{ 

include ". ./functions/charset.php"; 

include ". ./functions/basic.php"; 

dbconnect( "survey" ) ; 

include "functions.php"; 

define("LOADED_HEADER", "yes"); 



$result = safe_query ( "sel ect 1 from bl ocked_domai ns 
where ' $REM0TE_H0ST ' 1 i ke concat ( ' % ' , domai n ) 
and release_dt is null 

"); 



236 



Part III: Simple Applications 



if (mysql_resul t( $resul t , 0) > 0) 
{ 

print "<h2>sorry - your domain has been blocked from this 
page</h2>\n"; 

exit; 
} 

This preceding code contains information you're going to need before working 
with the heart of the application. The includes are clear enough. The includes have 
been put inside an if statement as a precaution. There is no need to reload the 
header once it has been loaded once. We can make sure that doesn't happen by 
creating a constant named LOADED_HEADER. If by chance, this page were loaded 
a second time, you wouldn't have to worry that includes would be imported more 
than once. 




Remember that PHP 4 has the include_once construct, which will ensure 
that no files are included multiple time. 



As we mentioned earlier, there is a facility here to block domains, and this appli- 
cation will be doing that off the $remote_host variable. This is hardly necessary, 
and it is easy enough to comment out this code. In order to understand this code, 
look more closely at the query, particularly the i i ke predicate. When we dial in to 
the net from my ISP (att.net), my REMOTE_HOST issomething like this: 119. san- 
francisco-18-19rs.ca.dial-access.att.net. When you block domains, you'll 

be doing it on the top-level domain— in this case, att.net. And this top-level 
domain is what will reside in the database. So the query will have checked on any 
number of wildcard characters prior to the top-level domain name. 

To achieve this you will need to concatenate the domain names with the % wild- 
card character. So, for instance, the query will work against xatt.net. This may 
seem somewhat different from your typical i i ke predicate. It's another powerful 
technique to use with your SQL. 

Also note that the start of the select statement doesn't contain a select count(*), 
instead opting for select 1. This is a good way of testing if any rows meet the con- 
dition of the where clause. If the where clause matches any number of rows, the 
query will return a single column with the value of 1, which in the programming 
world means TRUE. If there are no rows returned you know the where portion of 
the query had no matches. 

WEEKSTARTO This function creates a MySQL function to grab the day that starts 
the week. You use this in the application to pick a winner for the current week. 



Chapter 9: Survey 237 



function weekstart ($when="") 



if (empty ( $when ) ) ( $when = "nowO"; ) 

elseif ($when != "create_dt" ) ( $when = "'$when'"; ) 

return "f rom_days( to_days( $when) -dayofweek( $when) + 1)"; 



It works like this: the MySQL to_days( ) function returns an integer of the number 
of dayssincejanuary 1, 0000. dayofweek( ) returns an integer representing the day of 
the week (Sunday equals 1, Saturday equals 7). So the portion (to_days($now)- 
dayofweek($when) + l) will return an integer representing the Sunday of the week in 
question. The from_days( ) function then turns that number into a date. Here is the 
result of this query run on Thursday J uly 27, 2000 (the day this chapter was written): 

mysql> select f rom_days(to_days(now( ) ) -dayofweek(now( ) ) + 1); 
+ h 

| f rom_days ( to_days(now( ) ) -dayofweek(now( ) ) + 1) 
_i 1_ 

| 2000-07-23 

_i h 

1 row in set (0.00 sec) 

Note that the value passed here can be a string representing a date, it can be 
empty, or it can be a field from the users table— namely the create_dt field. 

COUNTRYLIST() This function creates a drop-down list of country names. 

function country_list () 
t 

$ c o u n t r i e s [ " " ] = " " ; 

$countries = array_merge( $countri es 

,db_val ues_array ( " count ri es" , "country" ) 

); 

return Icountries; 



Uses the db_vai ues_array( ) function (discussed earlier in this chapter, in the 
section "Reusable functions") to get an array of countries and their abbreviations. 

STATE_LIST() This creates a drop-down list of state names. 

function state_list () 
t 

$states[""] = ""; 



238 Part III: Simple Applications 



Istates = array_merge($states 

,db_val ues_array ( "states" , "state" , "statename" , "state" ) 
); 
return Istates; 



Uses the db_values_array() function (discussed earlier in this chapter, in the 
section "Reusable functions") to get an array of countries and their abbreviations. 

FETCH_QUESTION() This function grabs the contents of a row in the questions 
table and assigns the columns to global variables. 

function fetch_questi on ( $questi on_i d=" " ) 

{ 

if (empty ( $questi on_id) ) ( $question_id = 0; ) 

Iresul t = fetch_record( "questi ons" , "questi on_id" , $questi on_i d) ; 

return $result; 

} 

This will run the fetch_record( ) function and return from the database all the 
information regarding a particular question, based on the questionid. 

FETCH_USER() This function grabs the contents of a row in the users table and 
assigns the columns to global variables. 

function fetch_user ( $user_i d = " " ) 

{ 

if (empty ( $user_id) ) ( $user_id = 0; ) 

Iresult = fetch_record( "users" , "user_id" , $user_i d) ; 

return Iresult; 



Returns the result set based on a user_id. 



Interesting Code Flow 

There are a few pages in this application that could stand some explanation. 
However, you should be able to follow most of them if you understand the func- 
tions in the previous section. 



admin question. php 



This is a fairly lengthy page, and for good reason: it is used for adding, editing, and 
deleting questions in the database. The portion of the page that will be run will be 



Chapter 9: Survey 239 



determined by the values passed by forms or links. The first time through there will 
be no variables passed, so a list of the current questions will be presented along 
with a form for entering a new question. Each of the links to questions that already 
exist in the database look like this: 

<a href="admi n_questi ons . php?what=edi t&questi on_id=2" > 

When a link like this is clicked, and theadmin_questions.php script is run again, 
the very bottom of the script will run, as shown here: 

el se 
t 

$qform_title = "Edit A Question : $questi on_id" ; 

fetch_questi on($questi on_i d) ; 



print subti tl e( $qform_ti tl e) ; 

print start_form( "admi n_questi ons . php" ) ; 

print paragraph("<b>Question:</b>" , 
text_f iel d("question",$question,60)); 

Notice how you can get all the information associated with $question_id with 
one function call (fetch_question( )). Because of the way the functions have been 
created, this automatically gives you a global variable for $question. 

Next, go into this loop: 



Sacount = 0; 

if ( $questi on_i d > 0) 

t 

$result = safe_query ( "sel ect answer_id, answer 

from answers 

where quest i on_id=$questi on_id 

order by answer_id 

while (1 i st($ai d , $atxt ) = mysql_fetch_array ( $resul t ) ) 
t 

$acount++; 

print text_f i el cK "answer_text[ Sacount] " , "$atxt" , 60) ; 

print hidden_f i el d( "answer_id[ Sacount] " , "Said" ) ; 

print " ( $ a i d X b r > \ n " ; 



240 Part III: Simple Applications 

The number 10 here limits the number of answers to each question to 10. This 
block gets the answers for the selected question and prints them out inside text 
fields. Additional information is put inside hidden fields. When printed out the 
result for one answer will look like this: 

<input type="text" name="answer_text[l] " val ue="Answer" 

size="60" > 

<input type="hi dden" name="answer_i d[l] " value="10"> 

When this form is submitted, $answer_text will be an array. $acount will see 
that the key of the array is incremented by 1 for each additional form field. Note 
that we need to make use of a hidden form element here. That is because each 
answer requires three pieces of information: what the answer number is (1-10), the 
answer text, and if the answer came from the database, we need to know the pri- 
mary key of the row the answer came from. The hidden field will create an array 
named $answer_id. The value in each element of that array will be the primary key 
of the row storing the answer. The index of that array will be the match with the 
index of $answer_text. So in code it looks like this, 

$i = 1; 

$answer_text[$i ] ; 
$answer_id[$i ] ; 

You'd know that $answer_id[$i] contains the primary key of a row, and 
$answer_text[$i] is the answer text that belongs in that row. 

The previous section of code will print out form elements only where there is an 
answer. But you should offer blank form elements so the administrator can enter 
new answers. 



while ($acount < 10) 
{ 

$acount++; 

print text_f iel d( " an swer_text[$a count] " , " " ,60) ; 

print hi dden_f i el d ( "answer_i d[$acount]" ,0) ; 

print " < b r > \ n " ; 
} 

This will complete the form, giving all the blank elements you need. For these 
blank answers, the form will contain the following: 

<input type="text" name="answer_text[8] " value="" size="60" > 
<input type="hi dden" name="answer_i d[8] " val ue="0"Xbr> 



Chapter 9: Survey 241 



In these form elements, the value of the hidden field is set to 0. That way, when 
it comes time to process these form elements, the script will have something to 
evaluate: if $answer_id[$i] is equal to zero or empty ($answer_id[$i]), this is a new 
element. 

When the form is submitted this chunk of code will run. 

There will always be 10 elements to be looped through, so a for loop works 
nicely. 

for ($i = 1; $i <= 10; $i++) 
f 

if (! empty ( $answer_text[$i ]) ) 

First we make sure there is available answer text. 



$answer = cl eanup_text( $answer_text[$i ] ) ; 

if (empty ( $answer_i d[$i ]) ) 

{ 

$query = "insert into answers 

(questi on_i d , answer) 

val ues 

( Squesti on_id , '$answer') 

} 

If the element of $answer_id is not empty (which means it can't be equal to zero) 
an insert statement is run. 



e I se 



$query = "update answers 

set questioned = $question_id 

, answer = '$answer' 
where answer_id = " . $answer_id[$i ] 



safe_query ( $ query) ; 



Otherwise, if there was an existing answer, an update query will do the trick. 



242 Part III: Simple Applications 



admingetwinner.php 



Most of this file is readable. Your goal is to draw a qualified winner at random from 
the database. First you use the weekstart function (discussed earlier in this chap- 
ter in the section "Functions and code from /book/survey/header.php") to get the 
date that the current week begins. 

$qweekdt = weekstart( $weekdate) ; 

1 i st($thi sweek) = mysql_fetch_array(safe_query( "sel ect $qweekdt" ) ) ; 

print subti tl e( "Draw a winner for the week of $thisweek"); 

You then create a query that will determine who is qualified. As you can see, 
we've decided that in addition to signing in the last week, they need to have 
entered a name and an e-mail address, and a legitimate age. 

$query = "select name, email, user_id from users 
where week(create_dt ) = week( ' $thi sweek' ) 

and year(create_dt ) = year( ' $thi sweek ' ) 

and name is not null and name != '' 

and email is not null and email != ' ' and email like 

' %@% . % ' 
and age > 
and country is not null and country != ' ' 

$result = safe_query ( $query ) ; 
$tot = mysql_num_rows ( $resul t) ; 

With the total number of qualified entrants in hand, the script makes a couple of 
decisions to determine a winner. First it needs to account for occasions when there 
are no possible winners. 

if($tot ==0) 
{ 

echo "There were no entrants this week"; 



We already ran a select query that has the potential winners. The first row in the 
result set is row number zero. So if there's only one possible winner, row is the 
only row returned. 

el se 
{ 

if ($tot == 1) 

{ 

// if there's only one entry, they win. 



Chapter 9: Survey 243 



$wi nner 
} 



However, if more than one possibility exists, the random number generator is 
seeded, and a row number is pulled. Note that the total number of rows will be one 
greater than the final row number (because the first row is numbered 0). This is why 
the top range of the random number must by $tot-l. 



el se 



mt_srand((double)microtime( )*1000000) 
$winner = mt_rand(0, $tot-l) ; 



mysql_data_seek( $result, $w inner); 

list($name, $email, $user_id) = mysql_fetch_array ( $resul t ) ; 

$urlthisweek = rawurl encode($thi sweek) 

print paragraph( 

"<b>$name</b> $email " 

, "<b><font color=red>WINNER!</fontX/b> " 

, anchor_tag( "admi n_wi nners.php? we ekdate=$urlthi sweek" 
. "&what=noti fy&user_id=$user_id" 
, "Notify Winner" 

) 
); 

This last chunk of code gets the information needed about the winner and pro- 
vides a link to the admin_winners.php page that contains the correct userjd, the 
week associated with this prize, and the variable $what, which will identify which 
portion of the admin_winners.php page we want to run. 

There's more to this file, but you should be able to follow the comments included 
with the code. 



adminwinners.php 



We created a few pages to ensure that the winner selected is notified of the exciting 
news and that we give the notification in a way that gives some security. This isn't 
much, but to make reasonably sure that the person who claimed the prize was the 
person you intended, you would need to make use of a login system, and users of a 
silly little survey may not be interested in keeping track of yet another password. 

The best we can do here is try to make sure that if some immoral person sees the 
claim information one week, that person will not be able to easily spoof our system 
in future weeks. When we send the winner notification, we will include an eight- 
character claim code. This prize can only be claimed with knowledge of the code. 



244 Part III: Simple Applications 

To make things as secure as possible, we want to make sure this code is unique and 
very difficult to guess. 

mt_srand ((double) microtimeO * 1000000); 
$claim_code = substr(md5(uni qid( rand( ) ) ) , ,8) ; 

This uses the uniqueidQ and md5() functions to create string that is very random. 
There's little for a hacker to latch onto when trying to figure out how the string is 
constructed. md5() will create a string that is 32 characters, but that can be a bit 
unwieldy. So we're using substrQ to limit the string to 8 characters. 

The userjd, the claim code, and the week of interest are inserted into the win- 
ners table. 

Iquery = "replace into winners 

(weekdate, user_id, claim_code, notify_dt) 

val ues 
( ' $weekdate ' , $user_id, ' $cl aim_code ' , nowO) 



The winner is sent an email that is something like, where the claim code matches 
what has been entered in the database: http://mydomain.com/ciaim.php7ci aim_ 

code=ki 5g4ju9. 

If the user is interested, she will go to this page. 

CLAIM.PHP 

If the winner comes to claim. php, we first need to check that the claim code exists 
in the database. The query in the following code grabs queries the database to see 
if the claim code exists, and if it does, the query performs a join and returns the 
user information associated with the claim code. 

$user_id = 0; 

if ( lempty ( $cl aim_code) ) 

{ 

Iquery = "select u.user_id, u. email, w. weekdate 
from users u, winners w 
where w.claim_code = ' $cl aim_code ' 
and w.user_id = u.user_id 

Iresult = safe_query ( $query ) ; 



If the query returns data, the pertinent information will be assigned to variables. 

if (Iresult) 



Chapter 9: Survey 245 



1 i st( $user_id , $wi nner_emai 1 , $weekdate) 
= mysql_fetch_array ( $resul t ) ; 



If nothing was assigned to $user_id, we know that this is not a valid claim code. 

if ($user_id == 0) 

{ 

// we couldn't find a record corresponding to the claim_code 
// submitted (if any), print out an error and exit, 
print <<<E0Q 

<P> 

I'm sorry, that doesn't appear to be a valid claim code. 

The URL may not have registered properly. 

Make sure to copy the complete link into your browser and try again, 

or forward your original prize notification to $admin_emai 1 . 

</p> 

EOQ; 

exit; 



Once it is established that a claim code is valid, we want to do a bit of double- 
checking and make sure that the person who submitted this claim code knows the 
e-mail address that the notification was sent to. The application accomplishes this 
by sending a form asking the user to input the correct e-mail. That form is sent and 
processed by this page. When the form is submitted, the following code will execute. 

if ( ! empty ( $user_id) 
{ 

if ($user_email != $wi nner_emai 1 ) 

{ 
$notice = <<<E0Q 

I'm sorry, that email address doesn't match our records. 
Please try again, or forward your original prize notification 
to $admi n_emai 1 . 
EOQ; 

This comparison $user_email !=$winner_email will work because the query that 
ran at the top of the page retried the correct winner's e-mail, and the form submit- 
ted by the user creates $user_ email. If that comparison fails, an error message 
prints. However, if it does not fail, the following code updates the winners database, 
recording the time the prize was claimed and sends an e-mail to the winner letting 
them know that the claim was successful. 



246 Part III: Simple Applications 



el se 
{ 

Iclaimquery = "update winners set claim_dt = now() 

where user_id = $user_id 

and claim_code = ' $cl aim_code ' 

and weekdate = 'Iweekdate' 

Iresult = safe_query ( $cl aimquery ) ; 

if (Iresult && mysql_affected_rows( ) > 0) 

{ 
Smsgtext = <<<E0Q 

The prize for Iweekdate has been claimed by lemail. 
Confirm the prize at 

http: //$HTTP_HOST/book/survey/admi n_wi nners.php 

EOQ; 

mai 1 ( $admi n_emai l,"Prize Claim",$msgtext); 
print <<<E0Q 

<P> 

Thanks! Your claim has been accepted. Your prize should be on its 

way soon! 

EOQ; 

exi t ; 

The final portion of this page simply prints the form where the user will enter the 
e-mail. There's really no need to show that here. 



Summary 



There's quite a bit more code in the application, but there isn't anything that you 
shouldn't be able to figure out with some close scrutiny of the files and reading of 
the comments. Take a look at the complex_results.php page and its includes 
(age_results.php, state_results.php, and country_results.php) for a look at how 
MySQL aggregate functions can come in handy. 

This application contains quite a bit more complexity than theguestbook. In this 
application, we have a real database schema complete with related tables. In the 
course of the application we need to make use of queries that contain MySQL func- 
tions. (See Appendix I for more information on MySQL functions). 

The other notable thing seen in this chapter is the function set we've created for cre- 
ating common HTML elements. Whether you want to make use of these or something 
similar is up to you. You may prefer typing out the individual form elements, tables, 
and the like. But you will be seeing these functions used in the remainder of this book. 



Chapter 10 

Catalog 

IN THIS CHAPTER 

♦ Working with object-oriented code 

♦ Looking at database schemas 

♦ Working around MySQL limitations 

♦ Running shell commands from within PHP 



In the course of this chapter we are going to show oneway of creating an on-line 
catalog. You'll see how to present and administer an application that presents some 
typical retail items. 

We, the authors of this book, feel that you are an intelligent person, as well as 
someone with great taste in technical literature. We also believe that you picked up 
this book because you want to learn as much as you can about applications devel- 
opment with PHP and MySQL. That's why we're not wasting any time. Each chap- 
ter introduces additional challenges, or at least presents something new and 
different. This chapter will be no exception. 

If this chapter were to use the functions presented in the survey application in 
Chapter 9, there would be little new material to present here. All the application 
would need is a simple database schema, a few queries with some joins, and calls to 
the HTM L functions in the /functions/ folder. 

To keep things interesting, this application uses a completely different way of 
organizing code. This survey makes use of object-oriented, or 00 programming. 
However, we're not giving up on all of those functions. Some of them are just way 
too convenient (safe_query() comes to mind). 



Chapter 7 covers the concepts and nomenclature associated with object- 
oriented programming. In this chapter we assume that you read and under- 
stood that information. 




249 



250 



Part IV: Not So Simple Applications 



Determining the Scope and 
Goals of the Application 

The goals we have in mind for this application are pretty modest. Imagine for a 
moment that you own some sort of retail establishment that has goods you wish to 
hawk. Further, assume that you have no interest in actually conducting transac- 
tions over the Web. Maybe you are just paranoid about this new-fangled method of 
processing credit cards. Or perhaps you are running an elaborate tax-fraud scheme 
that requires you to deal solely in unmarked twenties. 




The code used in this catalogue will be re-used in the shopping cart appli- 
cation, where we will show how to process credit-card transactions. 



Whatever the circumstance, all this site needs to do is show your wares in logi- 
cal categories and breakdowns. You will hear more about the breakdown of the 
information when we discuss the database schema. 

The chief goal of this chapter is to create code that makes the best use of the 
object-oriented approach. The classes must make use of inheritance and encapsula- 
tion, and should make the task of writing individual scripts a whole lot easier. It's also 
important to think about modularity. The code created here will be reused in Chapter 
14, so we want to write code in a way that it becomes easily reusable elsewhere. 

We also wanted to throw in something really cool. One day on the PHP mailing 
list some guy named Rasmus mentioned a set of free software utilities that run on 
Unix that can be used to resize and otherwise manipulate all sorts of images. The 
PHP scripts in this application will interface with these utilities (called PBMplus) to 
automatically create thumbnails of the images of the catalogue items. We think 
you'll have to admit that this is pretty cool. 




Rasmus Lerdorf started the language that would evolve into PHP. He is one 
of the core developers,an active member of the mailing list.and an effective 
advocate of PHP software.The history of PHP is actually pretty interesting. If 
you have an MP3 player you can hear all about it from Rasmus and other 
core developers at: http://hotwired.lycos.com/webmonkey/radio/ 
php. html. 



Chapter 10: Catalog 



251 




PBMpluscan befound on the CD. 




Subscribe to the PHP mailing list.You'll learn about all sorts of groovy things 
you never would have guessed existed. If you are going to subscribe, be 
ready for the volume. The list can generate over 100 emails on a given day. 
Check Appendix H for a list of some of the other mailing lists. 



Necessary Pages 



The pages that display the catalogue aren't very extravagant. For navigational pur- 
poses there is a simple page that displays a list of categories. Figure 10-1 shows the 
category list. 



1 BagO'Stuff Cata 



Microsoft Internet Explorer 



Mi 



File Edit View Favorites lools Help 



Address | http:/AI 92. 1 68. 1 . 1 ^book/catalog/ 



"31 (f^Go 



Back Forward 



& © a 

Stop Refresh Home 



Search 



a 

Favorites History 



BagO'Stuff Catalog 



Here at Bag'O'Stuff, we've got so much stuff we don't know what to do with it. So buy some and 
help us clear out some space. 

What' ve We Got? 

• T- Shirts 

• Geometric Shapes 



J 



;ast 3 rt| ^ <P [a ■ jai £?3 <Sj IJJilF ±1 3 t $M°Q'^§y^ 5 t- < P 9:04am 



Figure 10- 1: Category list page 



252 



Part IV: Not So Simple Applications 



From the list of categories, the viewer of the page will click through to see a listing 
of products within that category. Figure 10-2 shows this rather underwhelming page. 



'3 Bag'O'Stuff: T-Shirts - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



Address | http:/V1 92.1 68.1 .1 /book/catalog/display. php?category_id=1 



~Z\ &So 



L 



Back Stop Refresh Home 



m. a 

Search Favorites History 



Bag'O'Stuff : T-Shirts 

Plain Old standby 

Oops! A sentiment we can all share 



J 



IS Start | <Q & ■ jg 'A *%! w\^ jgjij ^ftMog'^i^ t'Q% 



9:07 AM 



Figure 10-2: Products list page 

Finally, there is a page that lists the actual items for sale. Notice that a thumb- 
nail of each item is shown, and that alongside each item is a listing of variants of 
the item. In Figure 10-3, the items are t-shirts, and specific sizes are listed. 

Like all the applications in this book, this one has a series of administrative 
pages. Given what you have seen in the previous paragraphs and figures, it should 
be no surprise that the administrative pages create, delete, and alter information on 
the following levels: categories, products, items, and sub-items, the pages for which 
are shown in Figures 10-4, 10-5, and 10-6 respectively. 



Sub-items is the term that is applied to, for example, the sizes in which a spe- 
cific t-shirt is available.Sub-items represent slight variations of specific items. 




Chapter 10: Catalog 253 



y) Bag'O'Stuff: T-Shirts: Plain - Microsoft Internet Explorer 



File Edit View Favorites lools Help 



Add re s s | |S] http: //www. madf ish. com: SOSO/book/catalog/display. php?category_id=1 &product_id: 



^ T =^ T @ © tfl I a a I is- 

Back Stop Refresh Home Search Favorites History Mail Print 




Bag'O'Stuff : T-Shirts : Plain 

Old standby 
Price: 10.00 

Style Details 

black I^MiAvailable in: S, M, L, XL 



white 



Available in: S, M, L, XL, XXL 



:ast 3 rt| I <Q m ■ ijt A «Sj |J €|^g|M|ij 3 ®$hJoO>±a#<& ! W% am am 
Figure 10-3: Items list 



y Catalog Admin: Edit Category - Microsoft Internet Explorer 



Mi 



J File Edit View Favorites lools Help 



J <r" ' =1 @ [?) (2t I " J Address |fe http://1 92.1 68.1. 1/book/catalog/admin_category.php?cate^J j^Go 



Catalog Admin: Edit Category 



C*te&<"7 |T-Shirts 



Description -phe essential all-purpose item for any wardrobe. 



Save Changes | Delete Cate gory | Reset | 

Pro ducts 

- Plain 

■ Oops! 

■ A dd N e w Pro duct 

AdminPage 



zi 



S Done 

astartjlj <0 m ■ 1§ "A <Sd 



£ Internet 

Jjjgl jj i!3\a>»'ff^%<ft^J^1Sa 10:06 AM 



Figure 10-4: Category administration page 



254 



Part IV: Not So Simple Applications 



'3 Catalog Admin: Edit Product - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



Add re s s | SS] http: //www. madf ish. com: 8080/book/catalog/admin_product. php?product_id=1 



"3 






Category T- Shirts 
Product |p|ain 



Description Old standby 



Price 
Image 



S|io.oo 



Upload new file: |_ 



Browse... | 



Save Changes Delete Product Reset 



~3 
J 



Styles 

• black 

• white 

• Add Hew Style 

ia start | m*>Qm^'Aii MM Mzi I lis Wb.)»£> ^§^^>% 



9:12 AM 



Figure 10-5: Products administration page 




m start I *> & m ^ r A 4i MM &i±l ag^o^a^ t-o^a 9:14 am 



Figure 10-6: Item and sub- item administration page 



Chapter 10: Catalog 



255 



What Do We Need to Prevent? 

Unlike in the survey and the guestbook, there is no user interaction in this applica- 
tion. To the world at large the catalog is read-only. So we don't need to be quite as 
concerned with bored people adding unwanted tags and scripts to our pages. 

In this application there are more general concerns, the type of things that come 
up in every application: are there bugs, is the code efficient, are our data normal- 
ized properly, and other questions of that ilk. 



The Data 



For this application, we think it is useful to spend a few paragraphs discussing why 
we did not use what might seem to be the easiest and most obvious schema. 



A flawed data design 



If you were attentive in reading the previous pages, you will remember the break- 
down of the data. There are one or more categories, and each category will contain 
many products. Each product will have many styles, and each style can have a 
number of substyles. 

This might lead you to believe that a simple hierarchical structure of our tables 
would work just fine. Figure 10-7 shows the one- to-many relationships that would 
create this hierarchical effect. 



categories 



categoryjd 

category 

description 



products 



styles 



substyles 



productid 

categoryjd 

product 

description 

price 

image_src 



stylejd 

productjd 

style 

description 

price 

image_src 



substylejd 

stylejd 

substyle 

price 

description 



Figure 10-7: Flawed catalog schema 



Now consider what would happen if we were to add data to these tables. Let's 
take the example of t-shirts. There is a category for t-shirts, and a number of prod- 
ucts (different clever phrases stenciled on the t-shirts) for this category. Each prod- 
uct will come in a number of styles (colors), and each color will come in a number 
of substyles (sizes). Figure 10-8 shows what data in the hierarchical table form 
might look like. (Note that the tables have been simplified). 



256 



Part IV: Not So Simple Applications 



styles 



styleid 



sub- styles 



categories 



products 

productid 



categoryid 


category 


1 


t- shirts 


2 


shoes 



1 



style 



blue 



green 



red 



blue 



sub-styleid sub-style 



1 



XL 



XL 



styleid 



1 



Figure 10-8: Sample hierarchical data 



product 



J ust Say Oops 



I Love Milk 



categoryid 



> 



productid 



1 



Take a look at the substyles table in Figure 10-8. It is already starting to get a bit 
messy. Even with just a couple of t-shirts there is some repeating data. As you can 
see, size small (S) appears several times, and as we add to the catalog, more rows 
will be inserted and this table will get even messier. 

Thinking about the data a bit more carefully, you might notice something inter- 
esting: In the case of the t-shirts, the sizes are not dependent on the color of the 
t-shirts at all. If you remember back to Chapter 1, you will remember that a lack of 
a dependency is a bad thing. So the above data really aren't properly normalized. 

In fact, the sizes in which a t-shirt is available are not dependent on the color 
(style table) or the phrase on the t-shirt (products table). Size is, in fact, dependent 



Chapter 10: Catalog 



257 



on the category. All t-shirts (category_id=l) will come in S, M, L, or XL. Therefore, 
in the final schema there will be a relationship between the category table and the 
substyle table. 



Test your schemas. Before you go live with your own applications use some 
test data and see what happens. What seems right in theory could have 
some serious flaws in practice. 





Before you make a single table in your database, work with pencil and paper 
to draw out your tables and relationships. Erasing a line there is a lot less 
trouble than deleting a column. 



MySQL oddities 



Before we get to the final schema, this is a good time to point out one of the weird- 
nesses of MySQL. In Part I, we noted that MySQL doesn't support unions or sub- 
selects. Given these limitations, take a look at Tables 10-1 and 10-2, and tell me 
how you could find all the names in Table 10-1 that are NOT in Table 10-2. 



Table 10-1 NAMES 

firstname 

brad 

jay 

John 



Table 10-2 OTHER NAMES 



firstname 

brad 

jay 

William 



258 Part IV: Not So Simple Applications 

If MySQL supported sub- selects, this would work: 

select name from Names where first_name not in (select first_name 
from Other_names) 

However, in MySQL the best way to go about this is to perform an outer join. 
Something like this: 

select N . f i rst_name 

from Names N 

left join Other_names Otn on 

N.first_name = Otn . f i rst_name 
where Otn . f i rst_name is null 

With an outer join we can be sure that the Names table will be preserved in its 
entirety. Then, in the joined table, there will be non-null results where there are 
matching values. For instance, since 'J ay' is in both tables, that string will exist in 
both columns of the resulting query. But if there is no matching value, there will be 
a null value in thejoined table. 

You will also see sub- selects used for statements such as this: 

select first_name from Names where first_name in (select first_name 
from Other_names) 

In a case like this a straight join would produce the same result. 

select N.first_name 

from Names N, Other_names Otn 

where N.first_name = Otn . f i rst_name 

MySQL will not support unions until version 3.24. However, there is a way to 
work around this without too much difficulty in version 3.23. You can create a 
temporary table into which you can insert different select statements. For instance 
in other database packages you might do something like this: 

select first_name, last_name from tabl e_l 

uni on 

select first_name, last_name from table_2 

In MySQL 3.23 you would create a temporary table and then insert the results 
of the select statements into that table. The preceding code would be re-created 
like this: 

create temporary table name_union 

select first_name, last_name from table_l; 



Chapter 10: Catalog 



259 



insert into name_union 

select first_name, last name from table_2; 

Now that you have a table holding the union, you could perform a select on that 
table. The temporary table created will be very fast because it is not written to the 
disk; it is only stored in memory. 

Note that this syntax was not available in version 3.22. 

A better schema 

Now that we're done with that little digression, we'll get back to the structure for this 
application. Figure 10-9 shows the preferred schema, the one that we actually use. 



catalog admin 



username 
password 



categories 



categoryjd 

category 

description 



substyles 



products 



productjd 

categoryjd 

product 

description 

price 

image_src 



Figure 10-9: Catalog database schema 



styles 



stylejd 

productjd 

style 

description 

price 

image_src 



substyle id 

categoryjd 

substyle 

price 

description 




style substyle map 










stylejd 

substylejd, 

price 

description 

image_src 







260 



Part IV: Not So Simple Applications 



For starters, notice that there is a direct relationship between the categories and 
the substyles tables. The reason for this was explained earlier. The tables in the top 
half of the figure should make sense. They are the part of the hierarchy that still 
made sense. 

The neat and different thing in this application is the style_substyle_map table. 
If you are going through this book in order, this will be the first time that you 
encounter a many-to-many relationship. In this application a category can have 
many substyles (e.g., t-shirts come in S, M, L, and XL). And any style of shirt (e.g, 
"I Love Milk" in red) can come in zero, one, or more than one substyle (size). The 
style_substyle_map tracks that intersection. Figure 10-10 illustrates how the data 
come together. 



sub-style_id 


sub- style 


stylejd 


1 


S 


1 


2 


M 


1 


3 


L 


1 


4 


XL 


1 



productjd 



r- 2 



stylejd 



style 



blue 



green 



red 



blue 



substylejd 


stylejd 


1 


1 


3 


1 


1 


2 


2 


2 



Figure 10- 10: Sample data 



categoryJD 



category 



t-shirts 



shoes 



product 



lust Say Oops 



I Love Milk 



categoryjd 



productjd 



In Figure 10-10 the "J ust Say Oops" t-shirt in blue comes in two sizes: S and M. 
Listing 10-1 shows the statements that will create these tables. 



Chapter 10: Catalog 261 



Listing 10-1: Create Statements for Catalog Application 

# 

# Table structure for table ' catal og_admi n ' 
# 

CREATE TABLE catal og_admi n ( 

username varchar(50) NOT NULL, 
password varchar(255) NOT NULL 

); 

# 

# 

# Table structure for table 'categories' 
# 

CREATE TABLE categories ( 

category_id int(ll) NOT NULL auto_i ncrement , 
category varchar(255) NOT NULL, 
description text, 
PRIMARY KEY ( category_i d ) 



Table structure for table 'products' 
CREATE TABLE products ( 

product_id int(ll) NOT NULL auto_i increment , 

category_id int(ll) NOT NULL, 

product varchar(255) NOT NULL, 

description text, 

pri ce decimal (10,2), 

image_src varchar(255) , 

PRIMARY KEY ( product_i d ) , 

KEY product_category_id (category_i d) 



Table structure for table 'styles' 



CREATE TABLE styles ( 

style_id int(ll) NOT NULL auto_i ncrement , 

product_id int(ll) NOT NULL, 

style varchar(255) NOT NULL, 

description text, 

pri ce decimal (10,2), 

image_src varchar(255) , 

PRIMARY KEY (style_id), 

KEY styl e_product_key (product_id) 



262 Part IV: Not So Simple Applications 

); 

# 

# 

# Table structure for table 'substyles' 
# 

CREATE TABLE substyles ( 

substyle_id int(ll) NOT NULL auto_i increment , 

category_id int(ll) NOT NULL, 

substyle varchar(255) NOT NULL, 

pri ce decimal (10,2), 

description text, 

PRIMARY KEY ( substyl e_i d ) , 

KEY substyl e_category_key (category_i d) 

); 

# 

# 

# Table structure for table ' styl e_substyl e_map ' 
# 

CREATE TABLE styl e_substyl e_map ( 

style_id int(ll) NOT NULL, 

substyle_id int(ll) NOT NULL, 

pri ce decimal (10,2), 

description text, 

image_src varchar(255) , 

KEY map_styl e_key (style_id), 

KEY map_substyl e_key (substyl e_i d ) 

); 



Code Overview 



The code in this section is going to look substantially different from the chapters 
you have seen so far. Only the shopping cart (which, in fact, builds on this applica- 
tion) uses a similar method of organizing and accessing code. The other major dif- 
ferences are the use of utilities outside of PHP and accessing the file system. 



The object- oriented approach 



In the preceding applications, and the ones that follow, we make use of a proce- 
dural approach. That is, there is a series of functions, and each function performs a 
fairly specific procedure. In the actual application, there is little to do but call these 
functions. But in an application such as this, where the data are largely hierarchi- 
cal, it's helpful to make use of 00 programming's inheritance. After all, a Style is 
really an extension of a product. The style may be red, but "red" means very little 
without the inherited property of t-shirt, which comes from the Category table. 



Chapter 10: Catalog 



263 



If you use objects, the contents within the files called by URLs will be even 
sparser. Almost all of the work will be performed within the classes. Once you 
understand what actions the class files perform, there will be little else for you to do. 

To advocates of 00 programming, this is a major advantage. You, the program- 
mer, can get a class file and not really know what happens inside of it. All you need 
to know is what attributes it has and what its methods do. Then you can just include 
it in your application and painlessly make use of its sophisticated functionality. 



We said it in Chapter 7 but it's worth repeating here:You can write proce- 
dural code that encompasses all of the benefits discussed here. If you're not 
careful with 00 programming, yourcode can end up being much more dif- 
ficult to maintain and use. 




Accessing the filesystem 

You have probably noticed by now that in this book, almost all of our data are 
stored within the MySQL database. But even when you're using MySQL, there are 
times when you are better off using the filesystem for storage. Images (j pegs, gifs, 
pngs) are a perfect example. Even if your database supports binary data, there's lit- 
tle advantage in putting an image in a database. You need a database for storing 
and querying normalized data. In your database you are much better off storing the 
path to the image stored in your filesystem. That way it will be easy enough to fill 
in the src attribute in your <img> tag. 



Do not store images in a database. Put them on the filesystem. 




Uploading files 

This is the first application that lets users upload files; specifically, the administra- 
tors of the catalog will need to be able to upload images of their products. 
Uploading files is easy enough in PHP, but before you understand how the upload 
works, you will need to know how PHP handles uploaded files. 
In your HMTL page you will have a form, like the following. 



<form acti on="admi n_product . php" method="post" 
enctype="multi part/form-data" > 
<input type=file name="imagef i 1 e"> 
</f orm> 



264 



Part IV: Not So Simple Applications 




When you allow file uploads, you open yourself up fordenial of service (DoS) 
attacks. If you're not careful, someone could send many multi-megabyte files 
to your system simultaneously, which could bring your machine to a crash- 
ing halt. There are two things you can do about this. First is to put a hidden 
form field priorto your <input type="file">tag.The hidden field should look 
like this: 
<INPUT TYPE="hidden" name="MAX_FI LE_SIZE" val ue="1000"> 

where value is the maximum size allowed, in bytes.This is a reasonable first 
step, and could be of help in stopping someone who didn't know you had a 
size limit. However, this will not stop anyone with malicious intent. All they 
would have to do is look at the source code of your page and make the 
needed changes. The php.ini file contains the upload_max_filesize item. If 
you have accesstoyourphp.ini, you can set this to a number that you think 
is reasonable. By default, php.ini will allow 2MB uploads. 



When a file is specified and this form is submitted, PHP automatically creates a 
few variables. They would be: 

♦ $imagefile: the name of the file as stored in the temporary directory on 
the server 

♦ $imagefiie_name: the name of the file as it was on the user's machine 

♦ $imagefiie_size: the size of the file, in bytes 

♦ $imagefile_type: the mime type, in this case image/gif, image/png, or 
image/jpg 

The image will be stored in the temp directory specified in the php.ini file; if no 
temp directory is specified in php.ini, the operating system's default temporary 
directory will be used. 




The Paths and Directoriescategoryofthephp.ini controls many of the file 
upload options. 



Accessing outside utilities 

For this application, we need thumbnails for each of the images. The application 
could require the user to include both a full-size image and a thumbnail. But that's 



Chapter 10: Catalog 



265 



really not necessary, because the open-source world has provided all of us develop- 
ers with a utility that powerfully and easily manipulates images. It's called 
PBMplus. 




You can find the PBMplus package on the CD-ROM that accompanies this 
book. 



PBMplus contains several utilities that come in two basic flavors. One set of utili- 
ties transfers images to and from an intermediary format. So, whether you have a gif, 
a jpg, or a png, it must first be converted to the intermediary format, pbm. Another 
set of utilities alters the pbm files. These utilities do a variety of things: check the 
READM E file and the man pages that come with PBM plus to learn about them. Once 
you have made the .pbm file, you can convert it to whatever format you need. 

To use these utilities, you need to run shell commands from within your PHP 
script. For the purposes of this application, the system( ) function will be the best 
choice. More on that when we get to it in the code. 




There is potential danger when using functions that run shell commands.lf and 
information supplied from the user is transported to the shell commands, you 
should take great care to ensure that damaging commands cannot be exe- 
cuted. The escapeshellcmd() function will help http://www.php.net/manual/ 
function.escapeshellcmd.php examine shell commands.ln this application only 
people who are logged in will reach commands that access the shell. 



If you wish to output the results of PBMplus utilities directly to the browser, 

use the passthrough( ) function. 




Code Breakdown 



In 00 coding, good documentation is your best friend because, as already stated, it 
almost shouldn't matter how the classes you are using accomplish their tasks. You 
just need to know that they work. 



266 



Part IV: Not So Simple Applications 




PHPbuider has an excellent article on software that can help document 

classes: http:/ /www. phpbuilder.com/col umns/stefano20000824.php3. 



Objects in theory 

For instance, if we were to tell you about a class named Category, we could just tell 
you the following: 

Class Category: 

Inherits Base 

Properties: 

♦ products 
Methods: 

♦ LoadCategory: Loads all of the columns for the categories table into 
object properties, based on a unique categoryjd. Products belonging to 
the category will be available as sub-objects. 

♦ SaveCategory: Writes a new or updated category to the database. 

♦ FetchCategory: Based on a unique categoryjd, assigns all columns for a 
row to properties. 

♦ Del eteCategory: Removes a Category from the database based on a 
unique categoryjd. 

♦ PrintCategory: Writes out a category to the browser. 

Knowing this, and really nothing else, you could write a new script that dis- 
played a category. The script below assumes that a categoryjd was passed through 
the querystring, or via a POST. 



<?php 

$c = new Category; 

$c -> LoadCategory ( $category_id) ; 

$c -> PrintCategoryC); 

But if we left it at this, the learning experience would only be a fraction of what 
it should be. Of course we will go over the code in depth. But before we do, you 
should see how objects are constructed. 



Chapter 10: Catalog 267 



Objects in practice 



So far in this book you have seen variables of all shapes and sizes. There have been 
strings, integers, arrays, associative arrays, and all other kinds of fun stuff. 
However, here our variables are going to be quite a bit more complex. Take a look 
at these two lines, which we have already explained: 

$c = new Category; 

$c -> LoadCategory ( $category_i d) ; 

If you read the previous section, you know that this creates an object with sub- 
objects. It will be easier to understand with a visual representation. Figure 10-11 
shows a sample Category object. 



category Object ( 
([whatami] => Category 
[categoryjd] => 1 
[category] =>T- Shirts 

[description] => The essential all-purpose item for any wardrobe, 
[products] => Array ( 



[0] => product Object ( 



[1] => product Object ( 



[productjd] =>1 
[categoryjd] => 1 
[product] => Plain 
[description] => Old standby 
[price] => 10.00 



[productjd] =>2 

[categoryjd] => 1 

[product] => Oops! 

[desription] => A sentiment we can all share 

[price] => 10.00 



Figure 10- 11: Sample category object 

In Figure 10-11 you can see that the object contains not only properties. One of 
its properties (products) contains an array of other objects. Just to make this is 
clear, the following code will print, "You Bet". 

$c = new Category; 

$c -> LoadCategory ( $category_i d) ; 

if (i s_array ($c->products ) ) 



268 



Part IV: Not So Simple Applications 



if (i s_object($c->products[0] ) ) 
{ 

echo "You Bet"; 



The methods within the classes will be constructed so as to get these sub-objects. 
Functions that perform administrative tasks will need to account for this structure. 

Keep this in mind as you look at the other classes as well. A Products object 
might have an array of sub-objects for styles and a Styles object will have an array 
of sub-objects for sub- styles. 

Classes 

We designed the classes in this application so that most of them look and behave 
similarly. As you look at the classes, you should notice that all but the Base class 
have methods with similar names. Our hope is that once you understand one class, 
the workings of the others will be pretty clear. For this reason, we're only going to 
break down two classes in this chapter. Note that the each method in each class is 
extensively commented on the CD. So if you have a specific question as to the work- 
ing of a snippet of code, you will likely find the explanation within the comments. 

In the following pages, we will break down code in the following classes: Base 
and Product. For the other classes, we will only describe how to use the methods 
and properties. But once you understand the Product class, the other classes 
(Product, Style, and Sub- style) should be easy enough to figure out. 

The classes are pretty extensive; dumping them all into one file would make life 
unnecessarily difficult. It is a better idea to create a file called classes. php, which 
includes the actual classes. Here are the contents of classes.php 



include 
include 
include 
include 
include 



base_cl ass .php" ; 
category_cl ass .php' 
product_cl ass . php" ; 
styl e_cl ass . php" ; 
substyl e_cl ass .php' 



Please be sure that you have mastered the concepts in Chapter 7 before 
reading this section. 




Breaking the classes into includes also allows us to selectively reuse specific 
classes when we need them. This becomes important in Chapter 14, when we reuse 
some of these classes in creating the shopping cart. 



Chapter 10: Catalog 269 

BASE 

There is always a base class on which the inherited classes are built (though it's not 
always named Base). In this application, Base will contain a set of utilities that all of 
the other classes will make use of. The class is declared with the foil owing statement: 

class Base 



Now on to the properties. There are only three default properties. The following 
three come into play when dealing with images and thumbnails. 

♦ $image_src; 

♦ $thumb_src ; 

♦ $thumb_width 

Other properties will be created dynamically in course of the methods. 

METHOD SQL_FORMAT() This method helps build update and insert queries. 

function sql_format ($field) 
{ 

if ( empty ( $t hi s->$fi eld) ) 

return "null"; 
e 1 s e i f ( i s_n ume r i c ( $ t h i s - > $ f i e 1 d ) ) 

return $this->$field; 
2 I s e 

return $thi s - > $ f i eld. ; 



For the sake of a query, a variable may be null, a numeric quantity, or text 
string. Each of the data types requires a slightly different format within a query. 
Strings must be surrounded by single quotes. Numeric files can have no quotes, and 
for nulls, this method will return the string "null"— with no single quotes sur- 
rounding it. 

METHOD SET_IMAGE_SRC() This method both saves the uploaded image to the 
filesystem and creates the thumbnail. 



270 Part IV: Not So Simple Applications 

It is complex enough to break down section by section. First remember that the 
$fiie_src is the sole argument of the method call. $fiie_src is a string that will 
contain the path to the image. Most often, $fiie_src will be a string like 
"images/image_product_8_style_7". 

function set_image_src ( $f i 1 e_src=" " ) 
{ 

if ( lempty ( $thi s->imagef i 1 e) && $thi s->imagef i 1 e != "none") 
{ 

if ($file_src === "") 
{ 

$file_src = "images/" . uniqi d( "image_" ) ; 
} 

umask(2) ; 
$sizearr = GetlmageSi ze( $thi s->imagef i 1 e) ; 

Here, imagefile is a file that has been uploaded by a user. If a file itself exists but 
no file name is indicted by the $fiie_src argument, a variable called $fiie_src is 
created. Using the uniqueidO function, we create a file name that starts with 
"image_" and then contains some random characters. We want to make sure file 
names are unique, so we don't end up accidentally overwriting existing files. After 
that the umask is set (see your Unix man page for a description of umask), and then 
we use the get image size ( ) function. This function returns an array with four ele- 
ments: the first is the image height, the second is width, the third is the image type 
(1 = GIF, 2 = J PG, 3 = PNG, 4 = SWF), and the fourth is the height and width in a 
string that is ready for the <img> tag. This information is really helpful in that the 
script will be able to assign a correct extension, even if the supplied filename had a 
faulty extension, or none at all. 

if ($sizearr[2] == 1) { $file_ext = ".gif"; ) 
elseif ($sizearr[2] == 2) ( $file_ext = ".jpg"; ) 
elseif ($sizearr[2] == 3) ( $file_ext = ".png"; ) 
el se ( 

$file_ext = strtolower( 
substr($this->imagefil e_name 

, strrpos($this->imagefil e_name , " . " ) 
) 
); 



Using the information in the third element of the array, we determine the appro- 
priate extension. If the file type doesn't match any of the possible values returned 
by getimagesize( ), we parse the string containing the uploaded file's name. We 
take the characters from the final dot on, make them lowercase, and assign them to 
$file_ext. 



Chapter 10: Catalog 271 

$thumb_src = $f i 1 e_src . "_thumb" . $f i 1 e_ext ; 
$file_src .= $file_ext; 
copy($this->imagefile, $f il e_src) ; 

Now that we have a unique filename and the proper extension, we can put 
together filenames for both the main file and the thumbnail that we're going to 
make. Then the source file is copied from its temporary home to the full path on the 
filesystem, as indicated in $fiie_src. 

if ($file_ext == ".jpg") 

{ 

$cmd = " . . /catal og/makeimagethumb $file_src $thumb_src $ t h i s - 

>thumb_width" ; 

$out = system( $cmd , $err) ; 

If the image is a jpg, we call a shell script, using the system( ) function. Here it is: 

#!/bin/sh 

PATH=/usr/local/bin:/usr/new/pbmplus: $PATH 

export PATH 

rm ./$2 

djpeg ./$1 | pnmscale -xysize $3 $3 | pnmmargin -black 1 | cjpeg > 

./$2 

This script will be passed two text strings ($file_src and $thumb_src), which 
are assigned to variables $1 and $2. The shell script first removes the existing 
thumbnail (if it exists), and then goes about creating a new one. djped converts the 
jpg to the pbm format. The output of that is piped to pnmscale, which reduces the 
picture to a 50x50 px image; the output of that is piped to pnmmargin, which adds 
a black border. Finally, the resulting image is redirected to a file, as indicated by $2 

(or $thumb_src). 



The previous shell script could have been kept within PHP. It could have 
been written more like this: 

$path = "/path/to/pbmpl us/" ; 

$djpeg = $path . "djpeg"; 

Spnmscale = $path . "pnmscale"; 

$pnmmargin = $path . "pnmmargin"; 

$cjpeg = $path . "cjpeg" ; 

$cmd = " $djpeg $file_src | Spnmscale -xysize 50 50 | 

Spnmmargin -black 1 | $cjpeg > $thumb_src"; 

$out = system( $cmd , $err) ; 




272 Part IV: Not So Simple Applications 

If this shell script had produced an error, it would have been returned to the sys- 
tem function as the second argument, $err. 

if ($err) { print "<h4Xl i >cmd=$cmd <li>err=$err 

<li>out=$out</h4>\n"; } 
) 

el se 
{ 

copy ( $f i 1 e_src , $thumb_src) ; 
} 

$thi s->image_src = $file_src; 
$thi s->thumb_src = $thumb_src; 
) 
el se 
{ 

$thi s->set_thumb_src( ) ; 



If the file wasn't a jpeg, the uploaded file is copied to the place where the thumb- 
nail would be kept. Following that, we place the full path location of the file and 
the thumbnail to the image_src and thumb_src attributes. 

Note the final if block. This portion will run if a file has not been uploaded. 

METHOD SET_THUMB_SRC() This method sets a thumb_src property. It deter- 
mines this property based on the image_src property stored in the database. We are 
not storing the thumb_src property in the database, so when we need access to the 
thumbnail, we will need to run this method. 

The last period image_src is replaced with "_thumb.". If no period is found in 
image_src, thumb_src is set to image_src plus "_thumb". 

function set_thumb_src( ) 
{ 

$1 ast_peri od = strrpos ( $thi s->image_src , " . " ) ; 

if ( $1 ast_peri od === false) 

{ 

$thi s->thumb_src = $thi s->image_src . "_thumb" ; 
} 

el se 
{ 

$thi s->thumb_src = substr_repl ace( 
$ t hi s->image_src 
, "_thumb . " 
, $last_period 
, 1 



Chapter 10: Catalog 273 



METHOD BASE This is the constructor method for this class. As you can see it 
does little but make calls to a method called Construct. 

function Base ( $parent=" " , $atts = " " ) 
t 

$thi s->Con struct (get_object_vars ( $ pa rent) ) ; 

$this->Construct($atts) ; 



Notice the use of get_ot>ject_vars( ). It's a handy function that turns all of the 
object properties into elements in an associative array. So if an object is passed to 
this method (through the first argument), it can be passed to Construct as an array. 

METHOD CONFIRM_DELETE_FORM() This method prints a form that forces the 
administrator of the catalog to confirm that they want to continue with a delete. 
This comes into play when a deletion is indicated for an item that contains related 
child elements. For example, if the administrator indicates that a category should 
be deleted but the category still contains products, this form will print. 

conf i rm_del ete_form($message=" " , $1 abel =" " ) 
t 

$warning = "<b>Please confirm your delete request</b>" ; 

if ( ! empty ( Imessage) ) 

t 

$warning .= "- Imessage"; 

} 

Soutput = <<<E0Q 

<P> 

<form method="post"> 

Swarni ng 

<input type="hidden" name="conf i rm" val ue="Conf i rm Delete") 

<input type="submi t" name="submi t" val ue="$l abel "> 

</f orm> 

</p> 

EOQ; 

return loutput; 



274 Part IV: Not So Simple Applications 

METHOD CONSTRUCT!) This method is expecting an associative array. Most 
often, in the course of the application, the associative array will be the result of 
mysqi_fetch_array( ). Each of the keys in the array will become properties of the 
current object. Note that we have to run the set_thumb_src( ) property because 
the thumb_src is not stored in the database. 

function Construct ($atts="") 
{ 

if ( i s_array ( $atts ) ) 
{ 

while (listdna me, lvalue) = each($atts)) 
{ 

if ( lempty ( $val ue) && ! i s_array ( $val ue) ) 
{ 

$thi s->$name = lvalue; 



if ( lempty ( $thi s->image_src) ) 
{ 

$thi s->set_thumb_src( ) ; 



METHOD BASE() This is the constructor of the Base class. Its major job is to call 
the Contruct( ) method. If it receives an object in the first argument, the properties 
of the object are turned into an associative array with the get_ot>ject_vars( ) 
function before it is sent to Construct. The thumb_width property is set here 
because a value of a default property cannot accompany the property's declaration. 

function Base ( $parent=" " , $atts=" " ) 
{ 

$this->thumb_width = 50; 

if ( i s_object( $parent) ) 

{ 

$thi s->Construct(get_object_vars( $ pa rent ) ) ; 

1 

$this->Construct($atts) ; 



PRODUCTS 

Before we get started in explaining this class, let us re- state that this class is very 
similar to the other classes in this application. If you understand how this works, 
the rest of the classes should be relatively easy to figure out. 



Chapter 10: Catalog 275 



In Figure 10-11, a few pages back, you saw what a Catalog object looked like. 
Now, we'll show what a typical Product object looks like (Figure 10-12). 



product Object 
( 

[productid] => 1 

[categoryid] => 1 

[product] => Plain 

[description] => Old standby 

[price] => 10.00 

[style_count] => 9 

[substyle_count] => 9 

[styles] => Array 

( 
[0] => style Object 

( 
[substyles] => Array () 
[productid] => 1 
[categoryid] => 1 
[product] => Plain 
[description] => Old standby 
[price] => 10.00 
[stylecount] =>9 
[substylecount] => 9 
[stylejd] => 1 
[style] => black 

[image_src] => images/product_l_style_l.jpg 
[thumb_src] => images/product_l_style_l_thumb.jpg 



[1] => style Object 

( 
[substyles] => Array () 
[productid] => 1 
[categoryid] => 1 
[product] => Plain 
[description] => Old standby 
[price] => 10.00 
[stylecount] =>9 
[substylecount] =>9 
[stylejd] => 2 
[style] => white 

[image_src] => images/product_l_style_2.jpg 
[thumb_src] => images/product_l_style_2_thumb.jpg 



Figure 10- 12: Sample Product object 



276 Part IV: Not So Simple Applications 

Notice that the Product object contains and array of related styles. That array, 
named styles, contains Style objects. So at some point in this class, you can expect 
some code that will manufacture this structure. Specifically, somewhere in this 
class, we can expect the Product class to make calls to the Style class, in order to 
create these sub- objects. 

Additionally, if you look at Figure 10-11, you will notice that the Catalog object 
contains an array of Product objects. You can probably now see the parallel struc- 
ture we had mentioned. This is particularly important because as you look through 
the product object you should expect to see places where the Catalog class would be 
making calls to the Product class in order to create this structure. In fact, you will 
see that in the constructor of the Product class. 

METHOD PRODUCT!) This is the constructor of the class. It is very brief. 

function Product ( $parent=" " , $atts=" " ) 
{ 

$thi s->styl es = array(); 

$this->Base($parent,$atts) ; 
} 

Remember that we said that this will be called from the Category class. The$par- 
ent variable will contain the name of the parent, and $atts will be a row retrieved 
from the database. Though it's not important to show all of the code that will call 
to this method at this time, you should have an idea of what is needed to create the 
data structure seen in Figure 10-12. 

Somewhere in the Category class, a query to the database will retrieve a list of 
all of the products associated with a categoryjd, something I ike select* from prod- 
ucts where categoreyjd = $category_id. The result set of this query will be looped 
through and each time through the loop a row will be assigned to the second argu- 
ment in the call to the Product object. You will see an example of this later in the 
chapter, when the Product class makes calls to the Styles class. 

The call to BaseO will take each of the elements in the associative array and 
assign them to object properties. 

Note that this constructor will also run if a product object is instantiated within 
a script. But after being instantiated, the information associated with the prod- 
uctjd will not be automatically loaded. The FetchProduct( ) or LoadProduct( ) 
methods will be needed for that. It is the Load Product ( ) method that will make the 
Style class. But before we break that down, you need to see the FetchProduct( ) 
method. 

METHOD FETCH PRODUCT! ) This method grabs the row form the products table 
associated with the given productjd. It includes some error handling, for the event 
that no productjd or a bad productjd was given. Then, using the Construct 
method from the Base class, each of the returned fields is turned into a property of 
the Product object. 



Chapter 10: Catalog 277 



function FetchProduct ( $product_id=0) 
t 

if (! empty ( $product_id) ) 
{ 

$thi s->product_id = $product_id; 
} 

if (empty ( $thi s->product_i d) ) 
t 

$this->error = "no product_id specified for fetch"; 

return FALSE; 
} 
$result = safe_query ( "sel ect * from products 

where product_id = $thi s->product_i d 

if (!$result) 
{ 

return FALSE; 
} 

$row = mysql_fetch_array($result,MYSQL_ASSOC) ; 
$this->Construct($row) ; 
return TRUE; 



Notice the use of $this->error. By using defining an error in this way, in our 
scripts we can do the following: 

$p = new Product; 

if (!$p->FetchProduct(D) 

{ 

echo $p->error; 
} el sel 

echo "The product name is: $p->product" ; 



if the fetch didn't work out. The method will return FALSE, and you will have 
access to a meaningful error message. 

METHOD LOADPRODUCTO As mentioned earlier, this is the method that will cre- 
ate the structure seen in Figure 10-12. If you look at that figure, you will see there 
is quite a bit of information: the number of associated styles, the number of associ- 
ated sub- styles. Then there is the array that contains Style objects. 

function LoadProduct ( $product_id=0) 
( 

$ t hi s->FetchProduct($product_i d) ; 



278 Part IV: Not So Simple Applications 

Before doing anything, we will need to have access to all of the information 
associated with the productjd. As you saw, FetchProductc ) will take care of that, 
and will assign all of the columns to object properties. 

Iresult = safe_query ( "sel ect count( s . styl e) as style_count 

, count( s . pri ce) as styl e_pri ce_count 

, count(m. substyl e_i d) as substyl e_count 

, count(m. pri ce) as substyl e_pri ce_count 

from products p 

left join styles s on s .product_id=p. product_id 

left join styl e_substyl e_map m on m. styl e_i d=s . styl e_id 

where p.product_id = $thi s->product_id 

group by p.product_id 
"); 

$row = mysql_fetch_array($resul t ,MYSQL_ASSOC) ; 
$this->Construct($row) ; 

In the preceding code, the query will retrieve the number of associated styles and 
substyles, as well as a number of prices for styles and substyles. After the query is 
run and the row is fetched, the Construct ) method assigns each of the columns 
in the result to object properties. 

if ( $thi s->styl e_count > 0) 
{ 

$squery = "select * from styles where product_id = " 
. $thi s->product_id 

Isresult = safe_query ( $squery ) ; 

while ($srow = mysql_fetch_array ( $sresul t ,MYSQL_ASS0C) ) 

{ 

$this->AddStyle($this ,$srow) ; 



If there are styles for this Product, as indicated by style_count, a query is run to 
get all of the information from the styles table. Each row returned will be sent to 
the AddStyle( ) Method. There is only one line in the Addstyle( ) Method: 

$thi s->styl es[] = new Styl e( $parent , $atts ) ; 

Here a new Style object is created, and is placed in the styles array. This creates 
the structure seen in the previous two figures. 



Chapter 10: Catalog 279 

In case you're wondering, the Addstyie( ) method isn't really necessary here— 
this code could easily be in the LoadProducU ) method. We will show the reason- 
ing for this in Chapter 14. 

METHOD SAVEPRODUCTO In the administration of the catalog, you will need to 
update existing products and save new products. 

function SaveProduct( ) 
t 

if (empty ( $thi s->product_i d) ) 
t 

safe_query ( "i nsert into products 
(category_i d , product) 
values ( $thi s->category_i d , ' $thi s->product ' ) 

$thi s->product_id = mysql_i nsert_i d( ) ; 



If there is no existing productjd, we have MySQL assign one. By doing an insert 
into the products table, mysqi_insert_id( ) will return the primary key of the new 
row. Now that we have the row, we need to format the fields that were uploaded. 
Then we can run the query. 

$cleandsc = cl eanup_text( $thi s->descri pti on ) ; 

$this->set_image_src( "i mages /product_image_$this->product_id" ) ; 

$nullprice = $thi s->sql_format( "pri ce" ) ; 

$nul 1 image_src = $thi s->sql_format( "image_src" ) ; 

return safe_query( "update products 

set product = ' $thi s->product ' 

, description = '$cleandsc' 

, price = $nullprice 

, image_src = $nul 1 image_src 

where product_id = $thi s->product_i d 



METHOD DELETEPRODUCTO Finally, there needs to be a way to purge an exist- 
ing product. Note the single argument to this method. For safety reasons, this 
method forces the administrator to confirm any delete. To accomplish this, we make 
use of the confirmation form seen in the Base class. 

function Del eteProduct ( $conf i rm=" " ) 
( 

if (empty ( $thi s->product_i d) ) 



280 Part IV: Not So Simple Applications 



$thi s->error = "no product_id to delete"; 

return FALSE; 
} 
Iresult = safe_query ( "sel ect style_id from styles 

where product_id = $thi s->product_id 
"); 

As an added measure of safety, we are checking that the product intended for 
deletion does not have any child styles. If it does, the form in the base class will be 
printed with an appropriate error message. 

if (mysql_num_rows( $resul t ) > 0) 
{ 

if (empty ( $confi rm) ) 
{ 

$this->error = $thi s->conf i rm_del ete_form( 
"The product $thi s->product ( $thi s->product_id) still 
contains styles." 

. hi dden_f i el cK "category_id" , $thi s->category_id) 
. hi dden_f i el cK "product_i d" , $thi s->product_i d) 
, "Delete Product" 
); 
return FALSE; 
} 

We now loop through each of the stylesids returned from the previous queries 
and run queries that delete the styleids from the style_substyle_map table, the 
styles table, and the products table. Note that in other relational databases we could 
delete rows from many tables at once by using a join. MySQL, however, does not 
allow for joins when performing deletes. 

while ($row = mysql_fetch_object( $resul t ) ) 
{ 

safe_query ( "del ete from styl e_substyl e_map 
where style_id = $row->styl e_i d 

"); 
} 
safe_query ( "del ete from styles 

where product_id = $thi s->product_i d 
"); 
} 

return safe_query ( "del ete from products 
where product_id = $thi s->product_id 



Chapter 10: Catalog 281 

OTHER CLASSES 

Now that you have seen one class in its entirety, and have a feel for how the data 
structures are created, it would be a waste of paper, as well as your time, to lay out 
all of the other classes here. As we've said (about the 5 times now) they're designed 
to work similarly. If you understand one, you really understand all of them. 

If you'd like more detail on any of the remaining classes, see the comments 
within the files on the CD. In this section, we're going to tell you all you need to 
know to make use of the remaining classes. (Note that the Catalog class was 
described earlier.) 

Class Name: Style 

Extends Product 

Default Properties: 

♦ $substyle_count; 

♦ $substyle_price_ count; 

♦ $substyles; 

Methods: 

♦ Style. The class constructor. Takes two arguments: $parent and $atts. If 
$atts is an array, the BaseQ method will be called, assigning each of the 
array elements to Object properties. (Note, this method is similar to the 
Product method). 

♦ FetchStyle. Takes one argument, $style_id. Creates object properties for 
every row in the style table associated with the $style_id. (Note, this 
method is similar to the FetchProduct method). 

♦ LoadStyles. Takes one argument, $style_id. First runs FetchStyle and the 
creates an array, each element of which is a object containing sub- style 
information. (Note, this method is similar to the LoadProduct method). 

♦ SaveStyle. Takes no arguments, assumes a $this->style_id exists. Will 
both update existing styles and create new ones. (Note, this method is 
similar to the SaveProduct method). 

♦ DeleteStyle. Takes no arguments. Removes a style from the database. It 
will force confirmation if there are related substyles. It will delete that 
style after if confirmation is provided. (Note, this method is similar to the 
DeleteProduct method). 

♦ PrintStyleRow. Takes two arguments: $product_price, $product_dsc. Prints 
product information within a table row. If the arguments are equal to the 
equivalent fields in the Style, then those style fields are not printed. 

Class Name: SubStyle 
Extends Style 



282 Part IV: Not So Simple Applications 

Default Properties: 

None. 
Methods: 

♦ SubStyle. The class constructor. Takes two arguments: $parent and $atts. 
If $atts is an array, the BaseQ method will be called, assigning each of the 
array elements to Object properties. (Note, this is nearly identical in func- 
tion to the Product method). 

♦ FetchSubStyle. Takes one argument, $substyle_id. Creates object proper- 
ties for every row in the substyle table associated with the $substyle_id. 
(Note, this is nearly identical in function to the FetchProduct method). 

♦ SaveSubStyle. Takes no arguments, assumes a $th i s- >su bsty I e_ i d exists. 
Will both update existing styles and create new ones. (Note, this is nearly 
identical in function to the DeleteProduct method). 

♦ PrintSubStyle. Takes two arguments: $product_price,$product_dsc. Prints 
product information within a table row. If either of the arguments contain 
information, the values of those arguments will overwrite the information 
from the database in the printed row. 



Sample Script 



Now that you have understanding of the classes available, we'll show how they are 
put to work in one of the scripts. We'll look at display.php. This page is expecting 
at least a categoryjd to be passed via the querystring. If there is a categoryjd and 
a productjd, all of the product information will be printed. If only a categoryjd 
exits, a list of products is printed. 

<?php 

if (empty ( $category_id) ) 

{ 

header("Location: index.php") ; 

exi t ; 



The above code is just some error handling. We need to have a categoryjd. 

include " header. php"; 

$page_title = anchor_tag( "i ndex.php" , "Bag ' ' Stuff ") ; 

We are going to be using breadcrumbs for navigation. This is the fist link, which 
takes the user to the home page. Since we already know that there is a categoryjd, 
we can instantiate a Category object, which we're calling $c. Note that at this point 



Chapter 10: Catalog 283 



the $c really doesn't contain much, because we haven't used either FetchCategory 
or LoadCategory. 

$c = new Category; 



if (empty ( $product_id) ) 
t 

$c->l_oadCategory( $category_id) ; 

$page_title .= ": $c->category " ; 
include("start_page.php"); 

// print out a description of this category and a list of its 
// products 
$c->PrintCategory( ) ; 

If there is no productjd, we know that we are only concerned with the category, 
so we call the LoadCategory ( ) method. This creates a structure I ike the one seen in 
Figure 10-11. Then to print the category, all we have to do is call the Print 

Category( ) method. 



e I se 



This portion will run if there is a productjd. A product object is instantiated, 
and then LoadProduct( ) loads all of the associated style objects. See Figure 10-12 
to see the data structure it creates. Then the i_oadstyies( ) method grabs all the 
substyles associated with each of the styles. 

$p = new Product; 
$p->LoadProduct($product_i d) ; 
$p->LoadStyles( ) ; 

$c->FetchCategory( $p->category_id) ; 

$page_title . = ": " 

. anchor_tag( "di spl ay . php?category_i d=$c->category_i d" , $c- 
>category ) 

. " : $p->product" 

i n c 1 u d e ( " s t a r t_p a g e . p h p " ) ; 

$ p - > P r i ntProduct ( ) ; 



284 Part IV: Not So Simple Applications 

} 

include("end_page.php"); 

Then all we need is another Method call and the page is ready to go. 



Summary 



You might have found this chapter to be quite a handful. In addition to adding code 
for file uploads and accessing utilities outside of PHP, we've used a completely dif- 
ferent method for the organization of the code. 

The object-oriented approach used here may not be your cup of tea. And if it's not, 
you're in good company. Many people who work with PHP feel that object-oriented 
programming makes little sense in a Web development environment. But there are 
advantages. 

As you can see in this application, once the classes are created, there's very little 
you need to do get great functionality within your scripts. You will see in Chapter 
14 that we can take the code created here and build on it. 



Chapter 11 

Content Management 
System 

IN THIS CHAPTER 

♦ Creating an affordable content- management system. 

♦ Maintaining security in your databases 

♦ Anticipating shortcomings in MySQL's privilege scheme 



Welcome to our favorite application in this book. Don't get me wrong, we love 
the guestbook, we love the shopping cart, and we adore the problem tracker. But, as 
we spent our formative years dealing with Web sites that produced a steady stream 
of prose, we know the importance of having some sort of content- management sys- 
tem in place. 

Content- management systems come in all shapes, sizes, and costs. Depending on 
your needs or your company's, you might be inclined to make a five-figure investment 
in something like Vignette or a six- to seven-figure investment in Broadvision. But 
your choices don't end there. Zope (http://www.zope.org) and Midgard (http:// 

www.midgard-project.org/) and eGrail (http: //www. eg rail .com/) are just three of 

the open-source options for content management. 

Given all of these options, you might wonder why you should consider using the 
application presented here— why not just run off one of the aforementioned appli- 
cations? There is, in fact, an excellent reason. Content management is a field in 
which a high degree of customization is necessary. Your company's concerns are 
going to be distinct from any other's, and no matter what system you end up using, 
you are going to need to do a lot of coding to get your systems working just the 
way you want. 

If you decide on Vignette, you'll need to learn a nasty little language called Tel 
(pronounced "tickle"). If you want to use Zope, you will have to add Python to your 
repertoire. Midgard is a PHP-based solution, and there's no question that there's a 
lot of good code in there. It's open source, and presents a nice opportunity to con- 
tribute to the development of an increasingly sophisticated piece of software. 



285 



286 Part IV: Not So Simple Applications 

But you may just want something you can call your own, a solution that you 
know inside out, something that is built to solve the problems specific to your orga- 
nization. So take a look at what's available, see if your challenges, budget, and 
temperament make one of the ready-made solutions a good fit. If not, you can look 
at the framework and code presented here and adapt them to your needs, or maybe 
just re-code from scratch. 

Determining the Scope and 
Goals of the Application 

First off, you are going to need a site that presents content. For the sake of present- 
ing this content- management application, we've created a fairly basic site (which is 
in the book/netsloth/ directory on the CD-ROM). But whatever site you create is 
going to require all the design and editorial resources you can muster, and we're 
not going to worry about that too much here. 

Your content- management system is going to need to do several things. Its most 
obvious purpose is to offer an environment where writers, editors, and administra- 
tors can create new stories. Additionally, it must offer a flexible series of stages 
through which a story moves. For example, if originally implemented with a single 
editorial stage, the application must be flexible enough to accommodate an addi- 
tional editorial stage (or several of them) if needed. 

Additionally, this application must meet the various demands of a staff. There 
will be a series of writers, and some by-line information will be presented with each 
story. Further, in the editorial process staff members will be assigned specific func- 
tions. Various levels of permission will ensure that not everyone will have the 
authority to edit, proofread, or make a story available to the world at large. 

Finally, there must be a sort of super-user authority. A few select people will 
have the authority to add users and authorities to the editorial staff. 

Necessary pages 

First off, you need a site, a place where the articles will be displayed. As this isn't 
really the focus of this application, we've dealt with it very briefly. You will obviously 
need to code a site that fits your needs. Figures 11-1 and 11-2 show the Netsloth site 
in all its glory. 

This application manages content and the creators of the content. You will need a 
series of editorial stages and a series of users. Users will have access only to the 
stages that involve them. Figure 11-3 shows a page that lists sample stages and 
users. Figures 11-4 and 11-5 show pages that administer these rights and stages, 
respectively. 



Chapter 11: Content Management System 287 



■=?' Netsloth: Latest Stories - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



Mi 



SLOTH 



Netsloth: Latest Stories 



My Story 

2000-07-01 

A true story about nothing. 



':■!• . E:ln* •[■■:i»ftiti;t-; l-b 

2000-06-25 

In an article to be published this Monday in Science, the authors of a 10-year study at MIT have 
some surprising results. 



. 



zl 



;a start I <P a ■ ja 'A *§j jjfg] W\iA ^»WO'^ ^S^ y V% w.bb am 



Figure 11- 1: Netsloth index page 



'a Netsloth: Sky Blue, Scientists Say - Microsoft Internet Explorer 



Mi 



J File Edit View Favorites lools Help 



SLOTH 



Sky Blue, Scientists Say 

By Jay Greenspan 

A 10-year study has startling results. 

Oh, who knows. 
What will they say? 
Is it important? 



zi 



IS Start | gygl^^l jjfg] _^|±| i!3W«''ffO'^ --: J^ : t-=B 10:59 AM 



Figure 11- 2: View story page 



288 



Part IV: Not So Simple Applications 



'-' Administer Content Application - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



Address \^\ http: //madf ish. com: SOSO/book/content/admin. php 



"3 ^ Go 



Users 

Username Real Name 

brad Brad Bulger 

eddie Eddie Axelrod 

fired FredFlintstone 

jay Jay Greenspan 

joe Joe Blow 

Iroxanne J Roxanne T. Beauregard 



Email 

brad@madfish c om 
e ddie@nitpickers . c om 
fire d@b e dro ck. org 
j ay@ trans- city, c om 
joe@blow.com 



Create User 

Stages 

Stage Description 

Writing The storyis still being written. 

Editing The storyis written and is ready for review. 

Proofreading The story has been reviewed and is ready for spellche eking, URL checking, etc. 

Live The storyis available on the live site. 

;ast 3 rt| I <Q Pjj IBI jg & ^ |jJjgj^|2J||^U°'g^%^^EB^>^% 11:04AM 



Figure 11-3: Rights and stages page 



3 Administer Content : Users - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



Address \^\ http7/madfish.com:8080/book/content/admin_user.php?user_id=4 



Administer Content : Users 



User Info 



Username: T 



|jay 



Password: |~ 
Real Name: | JayGr( 
Email: 



j ay ©tran s-city ■ co in 



Stage Access 

Stage Description 

p' Writing The story is still being written. 

|^ Editing The story is written and is ready for review. 

|"~ Proofreading The story has been reviewed and is ready for spellche eking, URL checking, etc. 

|^| Live The storyis available on the live site. 

:a Start | ^ 3> & ■ J3 ^3 <Sj |J^^f#]iJ||^U°'g^%^^EB^>^% 11:05AM 



Figure 11-4: Rights administration page 



Chapter 11: Content Management System 



289 



U Administer Content : Stages - Microsoft Internet Explorer 


■ --l 


J File Edit View Favorites lools Help 


D 


Ad d re s s |«|] http: Mnadf ish. com: 80807 book/content/admin_stage. php?stage_id=1 


jj (f>Go 


Administer Content : Stages 

Stage Info 


1 


Stage: 


Writing 






Description: 


] 


User Acces 

User Name 
[^ brad 

E J° e 
|7 fred 
[7 jay 


s 

Brad Bulger 
Joe Blow 
FredFlintstone 
Jay Greenspan 


rtstaitiu mvmm^&'ii \\m fflieTj||w*»« 1 5 , #%<&if*ws% 


1 1 :06 AM 



Figure 11-5: Stages administration page 



This application also needs a workspace, a page where writers and editors can 
create stories, and where stories can work their way through the editorial process. 
The workspace will contain a few fields that identify the author, the date, the body 
of text, and other necessary information. Additionally, the stage of the editorial 
process that the story is in is indicated. This page is shown in Figure 11-6. 

Another important aspect of an editorial environment is versioning. It's very 
important to be able to track pieces as they work through the process. You'll want 
to know who is making bad changes. Remember: good project management is all 
about keeping your hands clean and assigning blame. (Note: be completely arbi- 
trary in your assignations— it will keep your staff off balance). Figure 11-7 shows 
the page that tracks versions, or the story history page. 

This application performs a few more tasks, but they are minor enough to over- 
look here. Anyway, now that we're well into the "Not- So-Simple" portion of the 
book, you should be able to figure this stuff out. Right? 



290 



Part IV: Not So Simple Applications 



J File Edit View Favorites lools Help 




Netsloth: Edit Story 2 



Headline 


|A Big Test 






Publish Date 


|2000_J|6 _l]|24_lJ 


Subtitle 


|This is the test you've been waiting for. 



Summary 



Body 



Who knew? Testing is important. 



Submit Body 
File 



<P> 

I think this is the first paragraph. 

</p> 

<P> 

Well, no, maybe this one is. 

</p> 

<P> 

No, the other one. Definitely. 

</v> 



Browse.. 



J 



J 



;flstart| <Q [jj ■ ^ & 4, Jj jj^ljj 3W°'gg'M S &&%.'% 



\ 



Figure 11-6: Editorial workplace 



'3 Netsloth: Story History for Story 1 - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



Editing as jay 

Netsloth: Story History for Story 1 



Modify Date Modified By Stage 


20000625190720 brad 


Live 


20000625190149 brad 


Proofreading 


20000625190131 brad 


Live 


20000625 1901 OS brad 


Proofreading 


20000625190103 brad 


Live 


20000625 1S3947 brad 


Proofreading 


20000625 1S3930 brad 


Proofreading 


Preview Storv | Edit Storv | 


MainPase 



aastarti m^^m^'A^i ; M fflliiHj 3W°'gg'M ^0^% 1 1:09 am 






Figure 11-7: Story history page 



Chapter 11: Content Management System 291 

What do you need to prevent? 

The major issue in this application is ensuring that users do only what they are per- 
mitted to do, and absolutely no more. To do this, the application makes use of 
MySQL administrative privileges. 

In all of the previous applications, there was a simple header file that called a 
function to log into the database. Each file ended up using the same dbconnectc ) 
call, with the same username and password. But that won't work here because dif- 
ferent users need different levels of access. 

Moreover, in this application some users are going to need the ability to grant 
access to others. Workers will come and go and their responsibilities will change. An 
administrator will need to be able to change these rights. Since we don't want every- 
body who logs into the database to have the same rights, this application will need 
the facility to have different people log in using different names and passwords. 

Privileges in MySQL are granted and revoked with the aptly named grant state- 
ment. It's fairly painless and is described in Appendix D. So before you move forward 
with this application, it might be worth taking a quick look at that appendix. 

It's worth mentioning here that in this application you will run into some of the 
weirder aspects of MySQL. If some of the design of this application seems a little 
strange, that's because it is. But that proverbial bridge will be crossed in due time. 

Designing the Database 

The schema represented in Figure 11-8 shows how this application divides its data. 
Keep in mind as you look at this that in database- development land there is usually 
more than one decent way to go about things. You might find a different way to 
arrange this type of data that works equally well. In fact, you may even prefer 
another way. That's fine with the writers of this book. We encourage independent 
thought and creativity, as long as it does not result in immoral or ungodly behav- 
ior. So normalize your data as you see fit, but in the process please don't violate 
any natural laws. 

Start by looking at the story_author_map table. Notice how it is on the many 
end of one-to-many relationships with two tables: the story table and the author 
table. This is a classic many-to-many relationship. The reason for this is as follows: 
It is possible that a single story will have more than one author, and it's more than 
likely that an author will contribute to more than one story. The forms as set up in 
the application do not at this point have a facility for adding multiple authors, but 
that's easy enough to change if you wish. It's best to start with this flexibility, even 
if you don't need it right away. 



292 



Part IV: Not So Simple Applications 





content admin 






username 
password 




authors 






authorjd 

author 

email 




story_ author map 


— >■ 


story id 


-< — 




authorjd 





content users 



stories 



storyj'd 

stagejd 

publishdt 

headline 

subtitle 

bylineprefix 

summary 

body 



story_ versions 



storyj'd 

modifyjjt 

modify jby 

stagej'd 

publishdt 

headline 

subtitle 

bylineprefix 

summary 

body 



userj'd 
username 
name 
email 



user stage map 



co ntent_ stages 




stagej'd 
stage varchar 
stagejJsc 



Note: each of these 
tables below 
corresponds to an 
entry in the 
content_ stages table. 
The tables will change 
as stages are added 
and deleted. 



editing stories killed stories proofreading stories 



storyj'd 



storyj'd 



storyj'd 



I ive_ stories writting_ stories 



storyj'd 



storyj'd 



Figure 11-8: Content manager schema 

The table story_author_map makes it possible for simple SQL statements with a 
couple of joins to get either a listing of specific authors and the stories they're 
involved with or specific stories and the authors who have worked them. For exam- 
ple, the following would get all the stories in the database written by J ay Greenspan. 



select s. headline, s . byl i ne_pref ix, a. author 
from stories s, story_author_map m, authors a 



Chapter 11: Content Management System 293 



where s.story_id = m.story_id and 
m.author_id = a.author_id and 
a. author = 'Jay Greenspan' 

The next thing to be aware of is the relationship between the stories table and 
the story_versions table. As we already mentioned, versioning is very important to 
a content- management system. In this case, it means that you must have access to 
old versions of articles. The way this schema is set up, the most current version of 
an article is always kept in the stories table. Additionally, every version of a story, 
including the most recent, is kept in the story_versions table. 

Now on to the tables that define the stages in the editorial process and the users 
who will have access to those stages: content_ users, content_ stages, and user_stage_ 
map. Once again, a many-to-many relationship ensures that the application will 
track the users and stages and the intersections of the two easily enough. Want to 
find all the users that have the rights to make content publicly available? This 
will work: 

select c.user_id 

from content_users c, user_stage_map m, content_stages s 

where s . stage=' Li ve ' and 

s.stage_id = m.stage_id and 

m.user_id = c.user_id 

This setup enforces some of the security needed in this application. If a user does 
not have a listing in the user_stage_map for a particular item, he will not be able to 
perform that task. 

You might be wondering who has the rights to grant these privileges. Those peo- 
ple are listed in the content_admin table. 

If you decide to tweak this application for your own purposes, you may find that 
using these tables may be all the security you need. If your users don't have access 
to the Unix box storing the database server, these tables should be enough. 
However, if your users are able to get at the data in others ways— perhaps by log- 
ging into the server and launching the MySQL command line client and running 
queries directly —you may need a bit more. 

In a case such as this, you want to grant users privileges to log into the database. 
So you have to give them usernames and passwords using the grant statement dis- 
cussed in Appendix D. Then no matter where the users are accessing the data from 
they will only have access to the appropriate data. 

Sounds great, right? Well, it would be great, but when you try to do this your 
application runs into some of the limitations of MySQL, and working around these 
limitations makes for some weird solutions. 

As of the writing of this book, MySQL is missing two things you could have 
made great use of here. First is a feature known as Views. A view is pretty easy 
concept to understand. When creating a view, an administrator defines a virtual 
table that may be a join of many tables and may only show some of the columns in 



294 Part IV: Not So Simple Applications 

the used tables. By restricting the user to a view of the data, the administrator can 
restrict what the user is permitted to see. Note that in most SQL servers, views are 
read only. 




There isn't a development tool on the planet that doesn't have its quirks. 
Every language and every database presents it own challenges.The more of 
these that you are aware of before you start writing your application, the 
better off you will be. 



The second thing missing from MySQL that would have been nice to use here is 
a more restrictive grant statement (please see Appendix D). As it stands, MySQL can 
grant authorities on several levels: for an entire installation, for a specific database, 
for tables within a database, or for columns within a table. While this may sound 
quite extensive, it is really not as complete as it could be. 

Consider this problem: in MySQL, using table- or column-level grants, how 
could you prevent user John, who needs to have only editorial privileges, from 
updating stories that are already live? The best way to go about it would be to have 
very specific privileges in the stories table. If the stagejd for Live is 4, you'd want 
a grant to John that gave him access to alter any of the rows in the stories table, 
except those where stagejd = 4. Other databases allow this, but MySQL does not. 

The best you could do with MySQL is provide backup security to the content_ 
users, content_ stages, and user_stage_map tables. This backup plan will only work 
within the PHP scripts that access the database. If you grant users permission, they 
will be able to log into the database via avenues other than the Web. And, as we 
already mentioned, the rights you can grant may not be as restrictive as you like. 
Grant rights at your own discretion. 

Here's how the secondary security works within the PHP scripts. When the 
administrator creates a stage, a row is added to the content_ stages table. In addi- 
tion, a create query makes a table with a single column to store the storyjds. For 
the proofreading stage, there is a table called proof readi ng_stori es. 

When new users are created, rights to these tables will be granted when appropri- 
ate. As a story works its way through the editorial process, the storyjd is written to 
the corresponding stage table, such as proofreading_stories. If the user does not have 
rights for that table, the update is rejected. 

Here's a quick example of how this would work. Say that you, the administrator 
of this content management system, decided that you needed to add another stage 
to your editorial process, called format_review. If you are logged into the applica- 
tion as a user listed in the content_admin table, you will have the rights to create 
this stage. The stage will first be added to the content_stages table. When you cre- 
ate the stage, you will indicate which of the users should have the authority to use 
this stage. Those users will be added to the user_stage_map table with a different 
row for each userjd/stagejd combination. This is our primary security, and if 



Chapter 11: Content Management System 295 

you're adapting this application for use in your system, you would probably leave 
it at this. 

However, using MySQL grants we're taking another step. MySQL grants won't 
allow us to say "grant all authority to user on stories table where stage_id=4". 
Instead we create an additional table named format_review_stories. Rights to this 
table will be granted to only those who need format review capability. As a story 
makes its way through the editorial process, the storyjd will be copied to the 
appropriate stage table. When it reaches format_review, the storyjd will be copied 
to the format_review_stories table. Because we can enforce grants on a table, we 
know that unauthorized users will not be able to access the format_review_stories 
table. If an unauthorized user tries to update the format_review_stories table, the 
query will be refused. 

This is hardly ideal. In fact, it's pretty sloppy. If we were to deploy this applica- 
tion in the real world it is doubtful that we'd actually try to make this work with 
grant statements. We'd probably just make sure that the user had no direct access to 
the database whatsoever and let the user_stage_map table make sure the users were 
properly restricted. However, we thought it would be useful to show an example of 
how to work with MySQLs grant statements from a PHP interface. 

Listing 11-1 shows the tables in the content administration application. 

Listing 11- 1: Create Statements for the Content Management System 

# Table structure for table 'authors' 

# 

CREATE TABLE authors ( 

authored int(ll) DEFAULT '0' NOT NULL auto_i increment , 

author varchar(50) , 

emai 1 varchar(255) , 

bio text, 

PRIMARY KEY (authored) 
); 

# 

# Table structure for table ' contert_admi n ' 
# 

CREATE TABLE content_admi n ( 

username varchar(50) DEFAULT " NOT NULL, 

password varchar(255) DEFAULT " NOT NULL 
); 

# 

# Table structure for table ' content_stages ' 
# 

CREATE TABLE content_stages ( 

stage_id int(ll) DEFAULT '0' NOT NULL auto_i ncrement , 
stage varchar(20) DEFAULT " NOT NULL, 



296 Part IV: Not So Simple Applications 

stage_dsc text, 
PRIMARY KEY (stage_id) 
); 



# Table structure for table ' cortent_users ' 
# 

CREATE TABLE content_users ( 

user_id int(ll) DEFAULT '0' NOT NULL auto_i increment , 

username varchar(20) DEFAULT " NOT NULL, 

name varchar( 50) , 

emai 1 varchar( 255) , 

PRIMARY KEY (user_id) 
); 

# 

# Table structure for table ' edi ti ng_stori es ' 

# 

CREATE TABLE edi ti ng_stori es ( 

story_id int(ll) DEFAULT '0' NOT NULL, 

UNIQUE story_id (story_id) 
); 

# 

# Table structure for table ' ki 1 1 ed_stories ' 

# 

CREATE TABLE ki 1 1 ed_stori es ( 

story_id int(ll) DEFAULT '0' NOT NULL, 

UNIQUE story_id (story_id) 
); 

# 

# Table structure for table ' 1 i ve_stori es ' 

# 

CREATE TABLE live_stories ( 

story_id int(ll) DEFAULT '0' NOT NULL, 

UNIQUE story_id (story_id) 
); 

# 

# Table structure for table ' proof readi ng_stori es ' 

# 

CREATE TABLE proof readi ng_stori es ( 
story_id int(ll) DEFAULT '0' NOT NULL, 
UNIQUE story_id (story_id) 



Chapter 11: Content Management System 297 



) 



# Table structure for table 'stories' 
# 

CREATE TABLE stories ( 

story_id int(ll) DEFAULT '0' NOT NULL auto_i ncrement , 

stage_id int(ll) DEFAULT '0' NOT NULL, 

publ i sh_dt date , 

headline varchar(255), 

subtitl e varchar(255) , 

byl i ne_pref ix varchar(20), 

summary text, 

body text, 

PRIMARY KEY (story_id) , 

KEY story_stage_key (stage_id) 
); 

# 

# Table structure for table ' story_author_map ' 
# 

CREATE TABLE story_author_map ( 

story_id int(ll) DEFAULT '0' NOT NULL, 

authored int(ll) DEFAULT '0' NOT NULL, 

PRIMARY KEY (story_id) , 

KEY author_story_map_key (author_id) 
); 

# 

# Table structure for table ' story_versi ons ' 
# 

CREATE TABLE story_versi ons ( 

story_id int(ll) DEFAULT '0' NOT NULL, 

modify_dt timestamp( 14) , 

modify_by varchar(20) DEFAULT " NOT NULL, 

stage_id int(ll) DEFAULT '0' NOT NULL, 

publ i sh_dt date , 

headl ine varchar(255) , 

subtitle varchar(255), 

byl i ne_pref ix varchar(20), 

summary text, 

body text, 

KEY story_versi on_key (story_id ,modi fy_dt ) 
); 



298 Part IV: Not So Simple Applications 

# Table structure for table ' user_stage_map ' 
# 

CREATE TABLE user_stage_map ( 

user_id int(ll) DEFAULT '0' NOT NULL, 
stage_id int(ll) DEFAULT '0' NOT NULL, 
PRIMARY KEY ( user_i d , stage_i d ) , 
KEY stage_user_map_key (stage_id) 

); 

# 

# Table structure for table ' wri ti ng_stori es ' 
# 

CREATE TABLE wri ti ng_stori es ( 

story_id int(ll) DEFAULT '0' NOT NULL, 
UNIQUE story_id (story_id) 



Code Overview 



At this point, we assume that you are getting comfortable with the way the applica- 
tions in this book have been constructed. You should be familiar with the functions 
introduced in Chapter 8, and the way PHP embeds MySQL commands within the 
scripts. 

As you work through, less and less of the code should require explanation. Thus, 
the descriptions of the code will deal only with those parts that are really new 
or tricky. 

Here, most of the newer-looking code will come from assigning the privileges 
discussed in the previous section. The application sends queries that you haven't 
used before. 



Code Breakdown 



Once again, the code in this application will make heavy use of the functions in the 
/functions/ folder. A lot of the code presented here will make calls to those functions. 

Reusable functions 

In this application, there is only one file with application-specific functions: /content/ 
functions.php. 



Chapter 11: Content Management System 299 

FUNCTIONS FROM /CONTENT/FUNCTIONS.PHP 

These will be used throughout the application. There will be many references to 
Chapter 9 in this section. 

LIST STORIES!) As mentioned in Chapter 10, in MySQL version 3.23 you can 
work MySQL's lack of support for unions by creating temporary tables. However, 
we developed all of the applications on this book using MySQL version 3.22, which 
does not support the create temporary table feature. This gets to be a problem when 
you need to print out the contents of two queries within a single table. If you look 
at the source code on the CD-ROM , you will see that the pages start by printing all 
of the stories currently within the workflow that the current user has access to. 
Following that, if the user has access to live stories, the live stories should be 
printed within the same table. 

To create this table, we need to call this function twice, passing the result iden- 
tifier of the query as the first argument. If the query has at least one row, the table 
header will be printed, and $in_table will be set to 1. The next time the function is 
called, the value of $in_table will be passed as the second argument. If the value of 
$in_table is 1 the header will not print. 

Again, if you are using MySQL version 3.23, use the create temporary tablefeature. 

function 1 i st_stori es( $resul t , $i n_tabl e=" " ) 
{ 

if (empty($in_table) ) ( $in_table = 0; ) 

while ($row = mysql_fetch_array ( $resul t ) ) 
{ 

if ($in_table == 0) 
{ 

print subtitl e( "Edit Stories"); 
p ri nt start_tabl e( ) ; 
pri nt tabl e_row( "<b>Stage</b>" 
, "<b>Story</b>" 
, " < b > P u b 1 i s h Date</b>" 
); 

$in_table = 1 ; 
) 
pri nt tabl e_row( $row[" stage"] , 

anchor_tag( "edi t_story .php?story_i d=" . $row["story_i d" ] 
, $row["headl i ne"] 
) 
, $row[ "publ i sh_dt"] 



return $ instable; 



300 Part IV: Not So Simple Applications 

FETCH_STORY() This function, like most of those in this section, makes use of the 
fetch_record( ) function, which is discussed in Chapter 9. In the call to the fetch_ 
record( ), a storyjd, which is the primary key of the table, is specified. Therefore, 
you can be sure that the query created by fetch_record will have only one row. The 
query in fetch_record will get all columns for the storyjd. 

function fetch_story ( $story_id=" " ) 
{ 

Iresult = fetch_record( "stories" , "story_id" , $story_i d) ; 

return Iresult; 



Because there is only one row, the fetch_record will call the set_resuit_ 
variabie( ) function (also discussed in Chapter 9). That function will make all of 
the columns for that storyjd available as globals. From the schema in Figure 
11-7, you can see that those columns are storyjd int, stagejd, publishdt, head- 
line, subtitle, byline_prefix, summary, and body. 

So after running fetch_story you can access any of the columns as global 
variables. 

fetch_story ( 1 ) ; 
echo $ h e a d 1 i n e ; 




The fetch_record( ) function isdiscussing in Chapter9. 



FETCH_STORY_VERSION() This function works almost identically to the fetch_ 
story ( ) function just described, the only difference being that multiple attributes are 
being passed to the fetch_record function. These are added to the SQL statement. 
Note that the modify_dt column is 14 digits (YYYYM M DDHHM M SS). The combina- 
tion of a storyjd and the second at which it was modified makes for a pretty good 
two-column primary key, so any query here should only return one row. 

f uncti on fetch_story_versi on ( $story_id=" " , $modi fy_dt=" " ) 
{ 

Iresult = fetch_record( "story_versi ons" 
, array ( "story_i d" , "modi fy_dt" ) 
, array ( $story_i d , " ' $modi fy_dt ' " ) 
); 
return Iresult; 



Chapter 11: Content Management System 301 

FUNCTION FETCH AUTHOR!) This function works similarly to the fetch_story 

function. It creates global variables from row retrieved from the author table. 

function fetch_author ( $author_id=" " ) 
t 

$result = fetch_record( "authors" , "author_i d" , $author_i d) ; 

return Iresult; 



FUNCTION FETCH_CONTENT_USER() This function also works identically to the 
fetcn_story function. It creates global variables from the row retrieved from 
the content_user table. 

function fetch_content_user ( $user_id=" " ) 
t 

$resul t = fetch_record( "content_users" , "user_i d" , $user_id) ; 

return Iresult; 



FUNCTION FETCH_CONTENT_ STAGED Once again, this function works identically 
to fetch_story. It creates global variables from a row retrieved from the content_ 
stages table. 

function fetch_content_stage ( $stage_i d=" " ) 
t 

$resul t = fetch_record( " con ten t_st ages" , "stage_id" , $stage_i d) ; 

return Iresult; 



Interesting Code Flow 



Since most of the more complicated stuff in this application has to do with main- 
taining users and stages, we will start the breakdown of code with the pages that 
take care of these stages. Later we will move on to the other features performed by 
this application. 



content/authenticate.php 



As we already mentioned, this application differs from the previous ones in that 
each user will be logging into the database with his or her own usernameand pass- 
word. The script that performs this login will need to be just a touch more flexible 
than the one you used in the other applications. 



302 



Part IV: Not So Simple Applications 



This application is going to use the same 401-type authentication used in the pre- 
vious examples, but here the values for $PHP_AUTH_USER and $PHP_AUTH_PW 
will also be the values used to log into the database. 



$PHP_AUTH_USER and $PHP_AUTH_PW are only available if PHP is installed 
as an Apache module. If you are working on Windows, you will not be able to 
use this type of authentication. 




The header.php file, which is included in every page in the content management 
system, contains the following code: 

if ( $authenti cati on == "admin") 
{ 

i ncl ude "admi n_authenti cate.php"; 
) 

el se 
{ 

include "authenticate.php"; 
) 
$this_username = $PHP_AUTH_USER; 

This if ... else block determines which authentication script will run. In pages 
that require administrative access you will see a variable assignment like the fol- 
lowing prior to the include of header.php. 

$authenti cati on = "admin"; 

If this does not exist, the authenticate.php will be included. 
Here are the contents of the authenticate.php file. 



Irealm = "Netsloth Content"; 

$errmsg = "You must enter a valid name & password to access this 

function" ; 

if (empty($PHP_AUTH_USER) 

|| ( !empty($newuser) && $olduser == $PHP_AUTH_USER ) 



( 



$what = empty($PHP_AUTH_USER) ? "login" 
"newuser($newuser,$olduser)" ; 
authenticate($realm,$errmsg.":$what'' 



Chapter 11: Content Management System 303 

$mylink = @mysql_connect( "1 ocal host" , $PHP_AUTH_USER, $PHP_AUTH_PW) 
or authenticate 
($realm, "Could not login to db as $PHP_AUTH_USER" ) ; 

mysql_sel ect_db( "netsl oth" ) ; 

As you look at the preceding code, keep in mind that in this script people using 
the application need to be able to change the usernames and passwords that they 
are using to log into the application— i.e., log in as a different user. If you go 
through the script step by step, you should see how to do it. We'll go through it 
line- by-line after one quick explanation. 

Within the index. php page, there is a submit button that, when pressed, will 
indicate that the user wants to login in under a different username and password. 
The following creates the form with the submit button. 

print paragraphic 

start_form( "i ndex. php" ) 

, hidden_field("olduser",$PHP_AUTH_llSER) 

, submi t_f iel d( "newuser" , " Log In As New User") 

, end_form() 



Using the functions described in chapter Chapter 9, this code creates a form, that, 
if submitted, sends the variables $oiduser and $newuser back to the index. php 
page. When the form is submitted and the variables are sent, they will hit this por- 
tion of the authenticate.php page. 

if (empty($PHP_AUTH_USER) 

|| ( !empty($newuser) && $olduser == $PHP_AUTH_USER ) 



The preceding if block will test true under two conditions. First, if the user has 
not yet logged in, because in that case $PHP_AUTH_USER will be empty. The other 
condition comes from the form we just discussed. If that submit button is pressed, 
$newuser will not be empty and $olduser will contain the value of $PHP_AUTH_ 
USER, meaning the user wishes to change her login name and password. If either of 
these is true, the following code will run: 



$what = empty ( $PHP_AUTH_USER) ? "login" 
"newuser($newuser,$olduser)"; 
authenticate($realm,$errmsg.":$what"! 



304 Part IV: Not So Simple Applications 

The preceding code is a trinary operator that will determine the value of $what. 
If $PHP_AUTH_USER is empty (meaning the user is not yet logged in) $what will 
be assigned a value of "login". Otherwise, what will be assigned is a string of 
"newuserQ" along with the values in $newuser and $olduser. The value of what is 
appended to the error message, which is sent to the authenticate!) function, which 
is discussed in Chapter 9. If the user cancels the login an error message like one of 
the following will appear: 

You must enter a valid name & password to access this f uncti on : 1 ogi n 

You must enter a valid name & password to access this 
functi on : newuser( Log In As New User.tater) 

At this point all that's left to do is connect to MySQL and select the database. 

$mylink = @mysql_connect( "1 ocal host" , $PHP_AUTH_USER, $PHP_AUTH_PW) 

or authenticate 

($realm, "Could not login to db as $PHP_AUTH_USER" ) ; 
mysql_select_db("netsloth"); 



content/ admin user.php 



This page, like many you have seen before, has many purposes. The exact portion 
of the script that will run will depends on the variables that are sent to the page. It 
will do the following: 

♦ Enable an administrator to create new users. 

♦ The information specific to a single userjd will be displayed, including 
the stages associated with that user. 

♦ Additional stages can be granted to an exiting user. 

♦ Rights to a stage can be revoked from a user. 

If the page is accessed without any variable information in the querystring or 
from POST, the form elements for user information will be blank. This information 
must be filled in before the form is submitted. When the form is submitted the 
admin_user.php page will be called again, this time holding the entered form data 
and with the $submit variable equal to "Save Changes". 

When submitted, the condition in the if statement at the top of the page will 
test true: 

if ( lempty ( $submi t ) && $submit == "Save Changes") 



Chapter 11: Content Management System 305 

As the same form updates an existing user's information and creates a new user, 
there is a second condition that must be tested. If the user's information must be 
updated, the form passes a userjd from a hidden form element; otherwise the 
$user_id variable is empty. The result of this statement decides whether the script 
is to perform an update or insert query. 

if (empty ($ use r_id) ) 
t 

safe_query ( "i nsert into content_users (username, name, email) 
values ( ' $username ' , '$name', '$email') 

$user_id = mysql_i nsert_id( ) ; 



e I se 



safe_query( "update content_users set 

username= ' $username ' , 

name= ' $name ' , emai 1 = ' $emai 1 ' 

where user_id = $user_id 
"); 



"); 

Note that when this section of the script is completed, the userjd is known: 
either it was passed from the form or it was retrieved with the mysqi_insert_id( ) 
function. 

That brings this script to a series of MySQL grant statements that are sent via the 
safe_query() function. Notice the first two queries, which are within if blocks. 
These test whether this is a new user who has previously been granted rights. If it is 
a user whose rights are being updated, she will already have an entry in the user 
table in the mysql database. If not, she will need to be placed in that table. Note 
that the "grant usage" query enters the person in the mysql table but gives no spe- 
cific rights to any databases or tables. 

$result = safe_query ( "sel ect 1 from mysql. user 
where User = '$username' 

$rows = mysql_num_rows( Iresul t ) ; 
if ($rows ==0) { 

safe_query( "grant usage on netsloth.* to $username"); 
} 

if (! empty ( Ipassword) ) 
t 

safe_query ( "set password for $username 



306 Part IV: Not So Simple Applications 

= password( ' Ipassword ' ) 
"); 
} 

Now that the user has an entry in the mysql table, the script removes all rights 
to the netsloth database. You do this in order to start fresh, and grant only the 
needed privileges. If you didn't delete every entry, you would need to go through 
and test whether each privilege already existed, and revoke those that didn't 
belong. In the end, wiping away privileges and granting only those indicated by the 
checkboxes is the easiest way to go. But before granting user-specific rights, the 
following code cleans out the mysql. tables_priv table and grants rights needed by 
every user of the application. 

safe_query ( "del ete from mysql . tabl es_pri v 

where Db = 'netsloth' and User = '$username' 
"); 
safe_query ( "f 1 ush pri vi 1 eges" ) ; 

safe_query ( "grant select.insert, update, delete 

on netsl oth . stories to $username 
"); 
safe_query ( "grant select.insert, update, delete 

on netsl oth . story_versi ons to $username 
"); 
safe_query ( "grant select.insert, update, delete 

on netsl oth . authors to $username 
"); 
safe_query ( "grant select.insert, update, delete 

on netsl oth . story_author_map to $username 
"); 
safe_query( "grant select on netsl oth . content_admi n 

to $username" ) ; 
safe_query( "grant select on netsl oth . content_users 

to $username" ) ; 
safe_query( "grant select on netsl oth . content_stages 

to $username" ) ; 
safe_query( "grant select on netsl oth . user_stage_map 

to $username" ) ; 

Now the script removes entries from user_stage_map (which is the primary 
source of security here) because again we want to start fresh. The stages were pre- 
sented in a series of checkboxes, and the ones that are checked when the form is 



Chapter 11: Content Management System 307 

submitted are passed as an array named $stages. The script loops through the array 
granting rights to the appropriate stage table (for example, proofreading_table) and 
making the needed inserts into the user_stage_map table. 

safe_query ( "del ete from user_stage_map where user_id = $user_id"); 

if ( i s_array ( $stages ) ) 

{ 

while (list(,$stage) = each($stages)) 
t 

$stage_table = strtol ower( trim( Istage) ) . "_stori es" ; 
safe_query ( "grant select.insert, update, delete 
on $stage_table to lusername 

$pquery = "insert into user_stage_map 
(user_id, stage_id) 
select $user_id, stage_id 
from content_stages 
where stage = 'Istage' 

safe_query ( Ipquery ) ; 



Finally this script prints out the appropriate user information (if existing user 
information exists) and the stages as a series of checkboxes. The checkboxes are 
checked if the user has rights for that stage. 

The following query is intended to work with the checkbox_field( ) function 
you created earlier. That function takes three arguments (for name, value, and 
matchvalue). If the second and third match, the checkbox will be marked as checked. 



fquery = "select distinct 

if(m.user_id is nul 1 ,'-', s . stage) as matchvalue 
, s.stage_id, s. stage, s.stage_dsc 
from content_stages s, content_users u 
left join user_stage_map m 

on s.stage_id = m.stage_id 
and m.user_id = u.user_id 
where u . user_i d=$user_i d 



308 



Part IV: Not So Simple Applications 



This query gathers all of the stages and does an outer join on the content_users 
table. If the user has been granted access to a stage, that stage name appears in the 
returned record set, in the matchvalue field. If not, a dash is returned in the field. 
When the checkbox_field() function is run later in the loop, the third argument will 
either be a dash or will have the same value as the stage field. The results of this 
query might look like this: 



H 1_ -| 

matchvalue | stage_id | stage 
_i h _| 

Writing 
Ed i ti ng 

Li ve 
Killed 



| stage_dsc 
-h 



1 


Writing 


Being written. 


2 


Editing 


Ready for review. 


3 


Proofreading 


Spel 1 checki ng , etc. 


4 


Li ve 


Story is available. 


5 


Killed 


Dead. 



This knowledge should allow you to read the rest of this script. And, of course, 
there are further comments included with the application on the CD-ROM . 



content/ edit_ story.php 



At almost 500 lines, this script is long, but it isn't especially complicated. Given the 
data structure we discussed earlier, it needs to create new stories and update exist- 
ing stories after they have been through an editorial pass. Along the way the script 
will need to check if the user has the rights to do the work on the story, and clean 
up text that a users put into the forms. 

The file should be readable by examining the comments within the page, which 
are supplied on the accompanying CD-ROM. There are quite a few decisions that 
need to be made in order to get this page to work correctly, and that adds to the 
length. But decisions that are made within the file are pretty straight forward. 
Additionally, there are quite a few insert and update statements. If you keep figure 
11-8 close by while you're reading through the code, this shouldn't be too tough to 
get through. 

This chapter has spent a fair amount of space discussing how to assign rights to 
a user using MySQL's grant statements. Hopefully at this point you see how those 
rights are assigned. The short piece of script following tests whether the current 
user has the rights to work on a story, based on the rights in the grants tables. 

It first gets the stage name, based on a stagejd, then creates the string of the 
table name by appending the stage name with "_table". Then a select statement 
runs that includes the table name you have just created. If that query is not 
allowed, the query will fail and return false. Also within the query, we are involv- 
ing the user_stage_map table. That table provides our primary security, and the 
user must have rights for the current stage in the user_stage_map table. If the user 



Chapter 11: Content Management System 309 

does not have rights defined in that table, the query will return no rows. If the 
query fails or returns nothing, an error will print and the script will exit. 

$result = safe_query ( "sel ect stage from content_stages 
where stage_id = $stage_id 

$result = safe_query ( "sel ect stage from cortent_stages 
where stage_id = $stage_id 

$stage = mysql_resul t( $resul t ,0) ; 

if ( ! empty ( Istage) ) 

t 

$stage_table = strtol ower(trim( Istage) ). "_stori es" ; 
Sresult = mysql_query ( "sel ect a.story_id 

from $stage_table a, contert_users u, user_stage_map m 
where a.story_id = $story_id 

and u.username = ' $thi s_username ' 
and u.user_id = m.user_id 
and m.stage_id = $stage_id 

if (Sresult === false || mysql_num_rows( $resul t ) == 0) 
t 

print subti tl e( "You do not have the ability to edit 
stories in the $stage stage. 

print paragraph(anchor_tag( "i ndex.php" , "Mai n Page")); 
exit; 



The other item of particular interest is the extensive text processing done in this 
script. This is an example of the type of processing you might need to do if your 
users are working with some sort of text processing tool (HTML editor, word 
processor). Every tool has its own little quirks that you will need to account for. The 
only way you are going to find out exactly what type of cleanup you need to do is 
examine the code created be the text editor in your workplace. 

For instance, you are not going to want to have <body> tags in the body of an 
article. 

$body = eregi_replace( " A .*<body[ A >]*>" , "" ,$body) ; 
$body = eregi_repl ace( "</body .*$","", $body) ; 

Of course, PHP's strip_tags() function could work for you, if you want to allow a 
limited tag set and remove all others tags. 



310 Part IV: Not So Simple Applications 

It's very important to run the stripslashes function on text that has been 
uploaded by a form and is being re-presented on an HTML page. In all likelihood, 
your magic_quotes_gpc setting (see Appendix B) will automatically escape and 
quotes or hyphens with backslashes in uploaded text. If you then send it out to the 
browser at that point, the user will see the backslashes on the screen. Furthermore, 
if the text is uploaded again, another set of backslashes will be added. 

$body = stri psl ashes( $body ) ; 
Iheadline = stripslashes($headline); 
Isubtitle = stri psl ashes( $subti tl e) ; 
$summary = stri psl ashes( Isummary) ; 

Starting at line 300 of the edit_story.php file there is a nice block of code that 
will do a couple of neat things. If it appears the user input the story without using 
<p> tags, the script will add them where it seems appropriate, assuming the user 
indicated paragraphs with newlines (hard returns). If the user did use <p> tags, the 
script examines the text, making sure that there are no funky spaces or malformed 
tags. We recommend that you look at the code and comments provided on the 
CD-ROM to get a good feel for how to do complex text handling. 



Summary 



In this chapter you saw some of the nifty things that can go into creating a content 
management system. Of course an application such as this can be far, far more com- 
plex than this. But this is a good start and presents a reasonable way to organize 
your code and database tables. 

We also made use of MySQL's grant statements when creating this application. 
As we've said throughout this application, the grant scheme that we've used here 
may not be terribly practical. However, it does provide a good example of how you 
could go about setting up a system where one login name and password for the 
entire application isn't enough. 

Also, make sure to take a look at some of the text handling code in edit_story.php. 
Some of the code provides an excellent example of what you can do with PHP's 
string handling functions and regular expressions. 



Chapter 12 

Threaded Discussion 

IN THIS CHAPTER 

♦ Adding community to your Web site 

♦ Using an advanced technique to write functions 

♦ Looking at other criteria to use when designing a database 



If you've created a Web site or are looking to create one, it's probably safe to 
assume that you would like people to return frequently to your pages. But as every- 
one in the Web industry knows, loyalty is fleeting, and people are always looking 
for something better, more engaging, or closer to their interests. After all, there's 
always someone with a better collection of Britney Spears photos. One way to keep 
the anonymous masses involved with your site is to offer your visitors a way to 
contribute to its content. If someone has accessed your site, it's likely that he or she 
has an opinion on the topic you are presenting. And, if my conclusions from 30- 
plus years of observation are correct, people love to share their opinions. Using the 
threaded discussion application in this chapter, you can create an area on your Web 
site where your users can share their opinions and interact with you and each other. 

Once you have this piece of your site up and running, you will be well on your 
way to creating your own Web community. I make special mention of the word 
community for two reasons. First, it is a huge buzzword within the industry. 
Everyone is looking to create a sense of familiarity and inclusion that will tempt 
users to return. The second— and perhaps more important— reason is that you, the 
webmaster, should know what you're getting yourself into. From personal experi- 
ence, I can tell you that "community" can be a real pain in the butt. On the Web, 
everyone is pretty much anonymous, and there is little consequence associated with 
antisocial behavior. Thus, in many discussion groups, opinionated windbags have a 
way of ruining a promising discussion. 

Before too long, you will undoubtedly see things that are mean or distasteful, 
and you must be prepared to deal with it. I'm not trying to scare you away from 
including a discussion list on your site. I'm just letting you know that you'll need to 
put some effort into administering it. Whether you monitor the list yourself or 
appoint someone to do it for you, somebody will need to make sure your users 
behave if you want it to be orderly and functional. 



311 



312 



Part IV: Not So Simple Applications 



Determining the Scope and 
Goals of the Application 

The purpose of any discussion board is reasonably simple. Any visitor to the site 
should be able to post a new topic to the board or reply to any of the existing top- 
ics. Furthermore, the board must be flexible enough to deal with any number of 
replies to an existing topic, or replies to replies, or replies to replies to replies, and 
so on and so forth. Put another way, this board must be able to deal with an indef- 
inite level of depth. The script needs to react appropriately whether the discussion 
goes one level deep, five levels deep, or ten levels deep. This will require some new 
techniques, both in your data design and our scripts. 

What do you need? 

You will need only two files to generate all the views needed for this application. 
But these two files will have very different looks depending on what information is 
displayed. 

The first file will display topics and their replies. The first time users come to the 
message board they will not know what threads they wish to read. Therefore a list 
of topics will be displayed. Figure 12-1 shows the list of top-level topics. 



U Display Topics - Microsoft Internet Explorer HE1Q 


File Edit View Favorites lools Help ■ 1 


Addre s s |g http: in 92. 1 68. 1 . 1 /book/discussion Aide*. php _^J £•> G o 


Back Forward Stop Refresh Home 


© a 

Search Favorites History 


Mail Print Edit 


topic list | new topic 


I love Snacks 


Sausages? 


Yet Another Topic 


i 


iastartii i^v^Bi^^ I \\W\ ■! wl-HlltSW*»«»%^<$ S&) r V*>% 7:09 PM 



Figure 12- 1: List of top- level topics 



Chapter 12: Threaded Discussion 



313 



Once a user chooses a topic, the page will list all of the posts within that topic. 
As you can see in Figure 12-2, the top of the page shows the text and subject of the 
post being read. Below that, immediate replies to that post are indicated with a col- 
ored border, and the text of the immediate replies is also printed. Figure 12-2 also 
shows that the application provides a subject, name, and a link to posts that are 
more than a level deep in the thread. You can see that it is rather easy to see who 
has replied to what. 



3 Display Topics - Microsoft Internet Explorer 



File Edit View Favorites lools Help 



Address | ^ http://1 92. 1 68. 1 . 1 /book/discussion /index. php?topic_id=1 



iHH 



"31 &G° 



> „ -► „ & © a 

Back Stop Refresh Home 



@ a 

Search Favorites History 



topic list | new topic 

Hove Snacks by JG (192.168.1.2) on 20000827102311 

Don't you just love food! I know I do. 

Reply to this 

Comment s| 

Re: I love Snacks by Donnie 092.168.1.2) 
Snacks are great, but they are nothing without a beverage. 

. Re: Re: I love Snacks by Walter (192.168.1.2) 

o Re: Re: Re: I love Snacks by Irving (192.168.1.2) 

> Re: Re: Re: Re: I love Snacks by Yoohoo Man (192.168.1.2) 
o Re: Re: Re: I love Snacks by Josh (1 92. 1 68. 1 . 2) 



:ast 3 rt| <g q» & ■ jg ^| <gj |][£) jll_^l±| 




Figure 12-2: Display of a thread 



This same page provides another view. If a user clicks through to a post that does 
not start a topic, the page will show all threads beneath. At the top of the page the 
script will print the top-level post (or root) and the post immediately prior to the 
one being viewed (or parent). Figure 12-3 shows an example of this view. 

Everything you saw in the previous figures was handled by one page. The sec- 
ond page will post threads to the board. This requires only a simple form that con- 
tains form elements for a subject, a name, and room for the comment. The form will 
need to be aware of where in the thread the message belongs. For new top-level 
topics a form without any context will be fine (see Figure 12-4), but for replies 
within an existing thread some context will be helpful (see Figure 12-5). 



314 



Part IV: Not So Simple Applications 



-' Display Topics - Microsoft Internet Explorer 



File Edit View Favorites lools Help 



Address |*S]http7/192.168.1.1/book/discussion/indeK.php?topic_id=3 



~ZJ <^ G ° 



<^ T # T @ © fl l © a 

Back Stop Refresh Home Search Favorites History 



Mail 



Print 



Edit 



root: I love Snacks 

parent: Re: I love Snacks 

Re: Re: Hove Snacks by Walter (192 .168 .1.2) on 20000827102507 

Beverages? No, I this is a snack discussion. Please go the bevnet.com to discuss liquids. 

Reply to this 

Comments: 

Re: Re: Re: I love Snacks by Irving 092.168.1.2) 
What about Yoohoo! I believe that is a liquid snack. 

• Re: Re: Re: Re: I love Snacks by Yoohoo Man 092.168.1.2) 
jgastartl mV&mjam^! |JHJB|^|TJ||^ t gU°%^^^t-^'gi 



7:12 PM 



Figure 12- 3: View further down a thread 



■=? Write Topic - Microsoft Internet Explorer 



File Edit View Favorites lools Help 



Address |*S]http7/192.168.1.1/book/discussion/write_topic.php 



<^ T # T @ ® ffl l fft a E- 

Back Stop Refresh Home Search Favorites History Mail 



~3 &Go 



topic list | new topic 



Subject: [ 

Your Name: [ 

Your Words 
Here: 



13 



Create | 

lastartl @*>&m^ r A^ ;.iHijBJ 



~3 



J 



o^#<i yf^t'-O'a 7:13 PM 






Figure 12-4: Form for posting a top-level topic 



Chapter 12: Threaded Discussion 



315 



y Write Topic - Microsoft Internet Explorer 



Mi 



File Edit View Favorites lools Help 



> „ -► „ & ® a 

Back Stop Refresh Home 



m. a 

Search Favorites History 



root: I love Snacks 

parent: Re: Hove Sn 

Re: Re: Hove Snacks by Walter (192.1 68. 1.2) on 20000327102507 

Beverages? No, I this is a snack discussion. Please go the bevnet.corn to discuss liquids. 

Care to comment? 



Subject: |Re: Re: Re: I love Snacks 



Your Name: [ 

Your Words 
Here: 



^ 



zi 



:flstart| gygl^^l [j] jg^jj Haff^'X^ ^|gt'O g B 



7:14 PM 



Figure 12-5: Form for posting a lower- level topic 

What do you need to prevent? 

As you've seen in previous chapters, you need to spend quite a bit of time making 
sure things work properly. We've talked before about the cross-site scripting bug. 
You'll surely want to prevent that from happening here. 

Unless every post is reviewed before it becomes available on the site, there is no 
good way of preventing users from posting nonsense and then replying to their 
own meaningless posts. This kind of thing can get pretty distracting— and again, 
there is no foolproof way of preventing it. However, you can make it a bit more 
obvious to other users that the nefarious poster is a jerk. For that reason, this appli- 
cation collects the IP of origin and displays it for every post. This isn't great pro- 
tection, but it is better than nothing. 




As one of the reviewers of the book pointed out, all users behind the same 
firewall will have the same IP, and users who dial-up through an ISP will get a 
different IP every time. Furthermore, the reviewer felt including the IP 
address of someone who actually had a static IP was bad etiquette. After 
thinking about it, I tend to agree. However, we kept this code in the applica- 
tion just to keep the example. You could remove it from your code in a mat- 
ter of seconds if you wish. 



316 



Part IV: Not So Simple Applications 



The Data 



Of all the applications discussed in this book, this one has what I believe is the most 
unexpected data structure. In fact when I (J ay) saw what Brad had come up with for 
this section I broke down crying, unable to deal with the beauty of it. 

I'll take a moment right hereto tell you a little secret about database development: 
Though you can usually figure out the structure of a database by going through the 
normalization process, sometimes you're better off concentrating more on the hoped- 
for end result. You'll see what I mean as you read the rest of this section. 

But before I show you what he created and why it works so well, let me give you 
an indication of what I was expecting— and why that would have been so prob- 
lematic. You might think that this application would start with a table looking 
something like Table 12-1. 



Table 12-1 


PROBLEMATIC ROOT_ TOPICS 






root_ 
topicjd 


root topic 
date" 


root_topic_ 
name 


root_topic_ 
subject 


root topic 
text 


1 


08/20/2000 


Jack 


Snacks Rule 


1 love em. 


2 


08/20/2000 


Edith 


MoreCheetos 


1 want my fingers 
orange. 


3 


9/1/2000 


Archie 


M&Ms 


Mmmmore. 



This table, as you can probably guess, would list the root topics. A simple 
SELECT * FROM root_ topics would return a recordset of all the root topics. This 
table doesn't allow for any data below the root level. To take care of this, I envi- 
sioned a structure where each root_ topicjd would be associated with another table. 
Whenever I inserted a row into the RootTopics table, I'd also run a CREATE TABLE 
statement to make a table that would store the replies to the root topic. 

For instance, all the replies to the "Snacks Rule" post would be stored in a table 
that looked like Table 12-2. This would work. There would be a one-to-many rela- 
tionship between the tables, and information would be available pretty readily. But 
now consider what would happen when somebody wanted to reply to one of these 
posts. I'd have to create yet another table. And what if I were to go another level or 
two deeper? I think it's easy to see that before long this would get completely out 
of control. With just a couple of active threads I could end up with dozens of tables 
that needed to be managed and joined. This would be no fun at all. 



Chapter 12: Threaded Discussion 



317 



Table 12-2 PROBLEMATIC TOPICS 



topicjd topic_date topic_author topic_subject topic_text 

1 08/20/2000 Ellen Re: Snacks Rule You betcha 

2 08/20/2000 Erners Re: Snacks Rule Indeed 



Now let's move away from this ill-considered idea and move toward Brad's 
sound plan. Think about what information needs to be stored for each post to the 
mailing list. Start with the obvious stuff. You need a column that stores the subject 
of the thread (for instance, "Nachos, food of the gods"), one that stores the author's 
name, and one that records the date the item was posted. So the table starts with 
these columns— I've thrown in some sample information in Table 12-3 and an 
auto_i increment primary key just to keep it clear. 



Table 12- 


3 


START OF A USEABLE TABLE 




post_ id 




Subject Author 


Date 


1 




Nachos rule Jay 


3/12/2000 


2 




Cheetos are the best Brad 


3/12/2000 



But of course this isn't enough. Somehow there needs to be a way to track the 
ancestry and lineage of any specific post. (Look again at Figure 12-1 if you are not 
sure what I mean.) So how are you going to be able to do this? If you are looking 
to track the ancestry of any particular thread, it would probably make sense to add 
a field that indicated the post that started the thread, which we're calling the root. 

Take a close look at Table 12-4. Start with the first row. Here the rootid is the 
same as the postjd. Now look at the third row. Here the rootid (1) matches the 
postjd of the first row. So you know that the thread to which row three belongs 
started with postjd 1— "Nachos Rule." Similarly, row 6 must be a reply to row 2. 



318 



Part IV: Not So Simple Applications 



Table 12-4 A MORE COMPLETE TABLE 


post ID 


Root 


ID 


Subject 


Author 


Date 


1 


1 




Nachos rule 


jay 


3/12/2000 


2 


2 




Cheetos are the best 


Ed 


3/12/2000 


3 


1 




Re: Nachos rule 


Don 


3/12/2000 


4 


1 




Re: Nachos rule 


Bill 


3/13/2000 


5 


5 




What about cookies 


Evany 


3/14/2000 


6 


2 




Re: Cheetos are the best 


Ed 


3/13/2000 



Now look at rows 1, 2, and 5. Notice that in these rows the postjd and rootjd 
are identical. At this point you can probably guess that whenever these two are the 
same, it indicates a root-level subject. Easy enough, right? The following SQL state- 
ment that would retrieve all of the root-level posts 

select * from topics where root_id=post_id . 

However, in this application, you will see us using a self join to get root-level 
topics. 



select distinct current .topi c_i d , current . parent_id , 
current . root_id , current . name , current . descri pti on , 
current . author , current . author_host , current . create_dt , 
current .modi fy_dt 

from topics current, topics child 

where current . topi c_id = chi 1 d . root_i d 

This join will have the same effect, finding rows where the topicid and rootjd 
columns are the same. We use it here because, even though it's a little slower, it's 
more flexible, and is easier to adapt in the event there are changes to the system. 

Now that you've added a rootjd field to the table you should know the begin- 
ning of a thread. But how can you get all the posts that came between the original 
post and the one you're interested in? Initially you may think that it would be pru- 
dent to add a column that lists the ancestors. You could call the column ancestors 
and in it you'd have a listing of topicids. It might contain a string like "1, 6, 9, 12". 
This would be a very, very bad idea. Why, you ask? Well, the most important rea- 
son worth mentioning is that you should never put multiple values in a single 
field— you'll open yourself up to all kinds of hassles. 



Chapter 12: Threaded Discussion 



319 




MySQL does have a column type that takes multiple values. It is called set. It 
is not used anywhere in this book because Dr. Codd would not approve. Do 
you remember Dr. Codd from Chapter 1? He's the guy who originally devel- 
oped relational database theory in the first place. 



So what options are you left with? Create another table to keep track of a post's 
lineage? I'm not sure how that would work, and as it turns out, it isn't necessary. 
The easiest thing to do is add a single column to the previous table that tracks the 
parent of the current post, as shown in Table 12-5. 



Table 12-5 AN EVEN BETTER TABLE 



post_ id 


root_ id 


parentjd 


Subject 


Author 


Date 


1 


1 





Nachos rule 


jay 


3/12/2000 


2 


2 





Cheetos are the 
best 


Ed 


3/12/2000 


3 


1 


1 


Re: Nachos rule 


Don 


3/12/2000 


4 


1 


3 


Re: Nachos rule 


Bill 


3/13/2000 


5 


5 





What about 
cookies 


Evany 


3/13/2000 


6 


2 


2 


Re: Cheetos are 
the best 


Ed 


3/14/2000 


7 


1 


4 


Cheetos, are you 
kidding 


Jeff 


3/15/2000 


8 


5 


5 


Re: What about 
cookies 


jay 


3/15/2000 



When you look at the first couple of rows in Table 12-5, you might see little dif- 
ference between the fields. And that sort of makes sense: if the postjd and the 
parentjd are the same, you already know it's a root level and that therefore the par- 
ent is irrelevant. Move your attention to row 7. Here you can see that root is row 1, 
"Nachos rule." That's easy enough. Now look at the parentjd, which is row 4. If you 
look at the parent of number 4, you will find that it's number 3— and further that 
the parent of that row is row 1, which is also the root. So with just this information 



320 Part IV: Not So Simple Applications 

you can follow a thread to its origin. A very simple script that traces a post to its ori- 
gin would look something like this: 

Select all fields from current topic 

If parent_id is not equal to and parent_id does not equal root_id 

Make parent ID current topic 

Go to line 1 

So that will about do it. Using this data structure, you can get all the informa- 
tion you are going to need. Throw in a couple of timestamps for safekeeping and 
it's all set. Here's the SQL statement that will create the table: 

create table topics ( 

topi c_i d integer default not null auto_i ncrement , 

parent_id integer default 0, 

root_id integer default 0, 

name varchar(255) , 

description text, 

create_dt timestamp( 14) , 

modify_dt timestamp( 14) , 

author varchar(255) , 

author_host varchar(255) 
, primary key topics_key (topic_id) 



Code Overview 



As we mentioned earlier, there are two main functions involved in this application: 
displaying a listing of posts and inserting a new post to the database. Thus, it 
should come as little surprise that at the base level there are only two files: 
display_topic.php and write_ topic. php. In addition to these files, you'll have a sep- 
arate file that stores all of your functions (functions.php). If you read the previous 
section, you probably won't be surprised to find that most of the effort involved in 
developing this application, and therefore most of the code we'll be introducing, 
relates to displaying the ancestors and children of a particular post. Keep in mind 
that a post can have any number of ancestors and any number of children. So your 
script will have to be flexible enough to deal with a post with one reply or twenty. 
The portion that writes a topic to the database should be pretty easy to deal with. 
In your form you'll need to have hidden fields that mark the rootid and parentjd, 
and you'll want to validate the contents of the forms, but other than that it should 
be pretty easy. Let's break it down. 



Chapter 12: Threaded Discussion 321 



Code Breakdown 



As per usual, most of the fun occurs in the functions. In fact, in this application the 
only two files actually referenced by URLs are practically empty. 

Reusable functions 

Again, this application will make use of the functions described in Chapter 8. There 
are just a few other functions, one of which uses a technique that requires some 
explanation. 

FUNCTIONS FROM /BOOK/DISCUSSION/FUNCTIONS.PHP 

The concept that is new to this application is called recursion. It comes up in the 
display_kids() function. 

DISPLAY_KIDS () Usually, in this part of the book, a function is displayed and 
then described. However, this function must be treated a bit differently because 
recursion can be somewhat difficult conceptually. So before I display the function, 
let's take a look at recursion. (If you already know your way around recursive func- 
tions, feel free to skim.) 

The important thing to keep in mind is that you have no idea how deep any 
thread will be: there may be one level of replies, or there may be twenty. So to 
properly lay out all the children of any one thread you need a very, very flexible 
script. It will need to do something like this: 

print current topic 

while the current topic has unprocessed child topics 
set current topic to next unprocessed child 
go to line 1 
end while 
if current topic has a parent 

set current topic to that parent and go to line 2 
el se 

exi t 
end if 

This must be repeated indefinitely until there are no other answers. But how do 
you tell a script to do something until it runs out of answers? The looping mecha- 
nisms we've discussed so far won't really work. The for.., while..., and do. ..while 
loops that we talked about in Chapter 2, and that we've used in the previous chap- 
ters, are of no help. 

If that isn't clear take a look at Table 12-6 and the code that follows. 



322 



Part IV: Not So Simple Applications 



Table 12- 


6 SAMPLE TABLE 








postJD 


root 


ID 


parentJD 


Subject 


Name 


Date 


1 


1 







Nachos rule 


jay 


3/12/2000 


2 


2 







Cheetos are the 
best 


Ed 


3/12/2000 


3 


1 




1 


Re: Nachos rule 


Don 


3/12/2000 


4 


1 




3 


Re: Nachos rule 


Bill 


3/13/2000 


5 


5 







What about 
cookies 


Evany 


3/13/2000 


6 


2 




2 


Re: Cheetos are 
the best 


Ed 


3/14/2000 


7 


1 




4 


Cheetos, areyou 
kidding 


Jeff 


3/15/2000 


8 


5 




5 


Re: What about 
cookies 


jay 


3/15/2000 



Say you want each level to be indented a little further than the previous one, 
with the HTM L blockquote tag. Now assume that you're calling the below function 
by passing the topicid of 7. 

function RecurForMedtopi c_i d) 



$query = "SELECT * from topics WHERE topic_id 
$result = mysql_query ( $query ) or 

di e( "Query failed"); 
$row = mysql_fetch_array ( Iresul t ) ; 
echo "<bl ockquote>" ; 
echo $row["name" ] , "\n"; 
RecurForMe($row[ "parent_i d" ] ) ; 



$topi c_id ; 



You know by now not to actually run a script like this— there's no error check- 
ing, and eventually, when there are no responses to the query, it will cause an error. 
I wrote this function so you can look at the last line. You see what it does: the func- 
tion calls itself. If you're not clear about the impact this will have, let's walk 
through it. 



Chapter 12: Threaded Discussion 323 

The first time through, given the $topic_id of 7, the query will return (surprise, 
surprise) row number 7. Then a blockquote tag and the name (J eff) will be printed 
out. Then the function will call itself, this time passing the parentjd of 4. The next 
time through, the query will return row 4, the next time through it will return row 
3, and finally it will call row 1. When done, the script output (before the error) will 
look like this. 

<bl ockquote>Jef f 
<bl ockquote>Bi 1 1 
<bl ockquote>Don 
<bl ockquote>Jay 

The DisplayKids function will work in pretty much the same way. It will call 
itself for as long as necessary. But in the final script you will have to take a lot more 
into consideration. For instance, the description field of immediate children will be 
printed out, but further down the ancestral path you'll show only subject and name. 
Before you get caught up in the larger script, let's look at how to change layout 
based on ancestry in your simplified script. 

function RecurForMe( $topi c_id , llevel = 1) 
t 

$query = "SELECT * from topics WHERE topic_id = $topic_id"; 

$result = mysql_query ( $query ) ; 

$row = mysql_fetch_array ( $resul t ) ; 

echo "<bl ockquote>" ; 

echo $row["name"] , "\n"; 

if (llevel == 1) (echo $row[" subject"];) 

RecurForMe( $row[ "parent_id" ] , $level + 1); 



I've added another variable (llevel) to this function to keep track of the level. 
The default value is 1, and it will be incremented each time through. The first time 
through the subject will be printed, but in iterations after the first, the subject will 
not be printed. 



Recursion is an expensive process; it takes up quite a bit of processortime.To 
prevent a system from being overwhelmed, you might want to limit the 
depth of any topic 



Armed with this information, you should be able to get through the Di pi ayKi ds 
function. There's a lot of info in there to assure good layout, but other than that, it's 
all pretty readable. There are some comments here to help you get through. 




324 Part IV: Not So Simple Applications 

function di spl ay_ki ds ( $topi c_i d=0 , $level=0) 
{ 

$child_count = 0; 

if (empty ( $topi c_id) ) ( $topic_id =0; } 

// retrieve topic records from the MySQL database having 
// this topic_id value in their parent_id column (i.e. those 
// for whom this topic is the parent_topic 

Iquery = "select topic_id, name, author, author_host, create_dt 
, description 

from topics where 

parent_id = $topic_id 

order by create_dt, topic_id 

Iresult = safe_query ( $query ) ; 
while 
(1 i st( $r_topi c_i d , $r_name , $r_author , $r_author_host , $r_create_dt 
, $r_descri pti on 
) = mysql_fetch_row( Iresul t ) 



if (llevel ) 
{ 

// non-zero level - use unordered list format 

if ( ! $chi 1 d_count) 

{ 

// this is the first child record 
// - begin the list 
print " < u 1 > \ n " ; 
} 

// begin the list item tag 
print " < 1 i > " ; 
1 

el se 
{ 

// zero (first) level - print inside a table 

if ( ! $chi 1 d_count) 

{ 

// this is the first child record: 
// - print out a header 
print "<b> Comments :</b><br>\n" ; 
} 

print start_tabl e( ) ; 
print " <tr bgcol or=skybl ue>\n" ; 
print " <td colspan=2 width=500>\n" ; 



Chapter 12: Threaded Discussion 325 



$chi 1 d_count++; 

if ($r_author == "") { $r_author = "[no name]"; ) 

if ( $r_topi c_i d != $topic_id) 

t 

print anchor_tag( 

"i ndex.php?topi c_i d=$r_topi c_i d" 
, $r_name 
); 
print " by <b>$r_author</b>" ; 

} 

el se 

{ 

// this should never happen, but just in case - 
// don't print a link back to this topic 
print "$r_name by <b>$r_author</b>" ; 



print " ( $r_author_host ) " ; 

if (llevel ) 

{ 

// not the first level - close the list item 

print " < / 1 i > \ n " ; 
} 

el se 
{ 

// first level - close the table cell & row containing 

// the topic name & author, then print out the text 

// of the topic and close the table 

pri nt " </td>\n" ; 

print " < / 1 r > \ n " ; 

print table_row( 

tabl e_cel 1 ( "&nbsp ; " , array ( "wi dth"=>2) ) 

, tabl e_cel 1 ( $r_descri ption,array("width"=>498)) 

); 

pri nt end_tabl e( ) ; 
} 

// display any child topics of this child, at the next 
// higher level 

di spl ay_ki ds ( $r_topi c_i d, $1 evel+1 ) ; 
} 
if ($level && $child_count) 



326 Part IV: Not So Simple Applications 



// if not the first level and at least one child was found, 
// an unordered list was begun - close it. 
print " < / u 1 > \ n " ; 



DISPLAY_TOPIC() This function displays information about a given topic. If no 
topicid is indicated, a list of the root-level topics is displayed. 

function di spl ay_topi c ( $topi c_i d=0 , $show_kids=l , $level=0) 
{ 

if (empty ( $topi c_id) ) ( $topic_id =0; ) 

Ifields = array ( "topi c_i d" , "parent_id", "root_id", "name" 
, "description", "author", "author_host" , "create_dt" 
, "modify_dt" 

); 

$query = "select distinct current .". impl ode( " , current.", 
Ifields); 

The next query that this script will send is a self join that uses aliases for the two 
copies of the table. One copy is named "current", the other "child". The implode 
function in the previous line will place the string ", current." between each array 
element. After this line of code runs, $query will be ready for the alias and will 
contain "select distinct current.topicjd, current.parentjd, current.rootjd, current, 
name, current. description, current.author, current.author_host, current.create_dt, 
current. modify_dt". 

The following portion executes if no topicid is indicated. It will display the 
root- level topics. 

if ( !$topic_id) 
{ 

Iquery .= " from topics current, topics child 
where current . topi c_id = chi 1 d . root_id 

Iresult = safe_query ( $query ) ; 

if ( !$resul t) 

{ 

print subti tl e( "Damn ! result = Iresult"); 
) 

el se 
{ 



Chapter 12: Threaded Discussion 327 

The query in the preceding snippet gets all of the root-level topics. The while... 
loop directly below prints each topic as an HTML anchor, something like <a 
href="index.php?topic_id=l">Topic name</a>. Finally, the portion below 

returns an empty array. 

while ($row = mysql_fetch_array ( $resul t ) ) 
t 

print paragraph(anchor_tag( 

"i ndex.php?topi c_i d=" . $ row [ "topi c_i d" ] 
, $row["name"] 
)); 



return array ( ) ; 



If a topicid is available, the following query gets the parent and root of the 
indicated topicid. An outer join assures that the information regarding the current 
topic is returned by the query. 

$query .= ", parent. name as parent_name 

, root. name as root_name 
from topics current 
left join topics as parent 

on current .par en t_i d = parent . topi c_id 
left join topics as root 

on current . root_id = root . topi c_id 
where current . topi c_id = $topic_id 

$result = safe_query ( $query ) ; 

if (!$result) 

{ 

print subtitle("Damn! result = Iresult"); 

return array ( ) ; 
} 
1 i st( $topi c_id , $parent_id, $root_id, $name 

, $description , $author, $author_host 

, $create_dt, $modify_dt, $parent_name 

, $root_name 
) = mysql_fetch_row( $resul t ) ; 



328 Part IV: Not So Simple Applications 

if (lauthor == "") ( $author = "[no name]"; ) 

if ($root_id != $topic_id && $root_id != $parent_id) 
{ 

if ($root_name == "") { $root_name = "[no topic name]"; ) 
print paragraph( 
"<b>root:</b>" 
, anchor_tag( "i ndex.php?topi c_i d=$root_i d" 

, $root_name 
) 
); 
) 

If there is a parent topic, the name of the topic is printed, along with a link to it. 

if ( lempty ( $parent_name) ) 
{ 

print paragraphs 
"<b>parent:</b>" 

, anchor_tag( "index.php?topi c_i d=$parent_id" 
, $parent_name 



} 

// print out the current topic 

print paragraphic "<b>$name</b> by <b>$author</b> ( $author_host) 

on <b>$create_dt</b> 
"); 
print paragraph($description) ; 

if ($show_kids) 
{ 

// print out a link to where the user can reply to 
// the current topic 
print paragraphic 

anchor_tag( "wri te_topi c.php?topi c_i d=$topi c_id" 
, "<b>Reply to t h i s < / b > " 



// now display any children of the current topic 
print paragraph(di spl ay_kids($topi c_id , llevel)); 



Chapter 12: Threaded Discussion 329 



// return information retrieved about the current topic 
return array ( $root_id , $parent_id, $name); 



CREATE_TOPIC() This function inserts the data taken form a form and inserts it 
into the database. As mentioned earlier, we are taking the IP address from the 
Apache $REMOTE_ADDR variable, which will also be inserted in the database. 
Many of the fields (for instance, $root_id) will becoming from hidden form fields. 
And rootjd will beset to if the user is attempting to create a new top-level topic. 
In those cases the parentjd will need to be set to the same value as the topicjd. 

function create_topic ($name="[no name]", $author="[no author]" 
, $descri pti on=" [no comments]", $parent_id=0 , $root_id=0 

) 

{ 

global $REMOTE_ADDR; 

$name = cl eanup_text( $name) ; 

Sdescription = cl eanup_text( $descri pti on) ; 

$author = cl eanup_text( lauthor ) ; 

$query = "insert into topics 

(name .descri pti on , parent_id, root_id, author, author_host) 

val ues 

( ' $name ' , 'Sdescription', $parent_id 

, $root_id, '$author', ' $REMOTE_ADDR' 



$result = safe_query ( $query ) ; 

if (!$result) 

{ 

print subti tl e( "hey ! insert failed, dammit."); 
} 
$topic_id = mysql_i nsert_i d( ) ; 

if (! empty ( $topi c_i d) ) 
t 

if ($root_id == 0) 
{ 

safe_query( "update topics set root_id = $topic_id 
where topic_id = $topic_id and root_id = 



330 Part IV: Not So Simple Applications 

el se 
{ 

print subti tl e( "Hey ! that didn't work."); 
} 

return $topic_id; 
} 

This function simply inserts the data into the database. All the information wil 
be coming from an HTM L form. 



Other Files 



If you understand the functions in the preceding section, the other files will be a 
piece of cake. In fact, they're so easy we'll only look at one. 



index.php 



As you can see, this file contains almost nothing. The displ ay_topic( ) function 
does all the heavy lifting. 

<?php 

include " header. php"; 

include "start_page .php" ; 

di spl ay_topi c( $topi c_id) ; 

include "end_page. php" ; 
?> 



Summary 

If you would like to see how the rest of the code comes together, take a look at the 
accompanying CD. The other files are well-commented and should be relatively 
easy to follow. 

You should come away from this chapter with the understanding of two con- 
cepts. The first is recursion. Recursion is a nifty tool that can be very helpful at 
times. If you'd like to see another example of recursion at work, check out the 
recurse_di rectory ( ) function in Appendix G, which will display all of the con- 
tents on a Web server. 

The other key thing is the way we went about organizing the data. We didn't fol- 
low a strict normalization procedure, like the one described in Chapter 1. Here we 
were more concerned with what gets the job done. In the end that's what all us 
application developers are trying to do, right? 



Chapter 13 

Problem Tracking System 

IN THIS CHAPTER 

♦ Designing a tracking system 

♦ Protecting yourself from redundant data. 

♦ Creating a site that has both publicly available and private portions. 



Got problems? Don't worry, we've all got problems. Relationships falter, bosses 
make capricious demands, and family —oh, we all know about family. Sadly, in the 
crazy lives that we all live, PHP and MySQL can do nothing to make your 
girl/boyfriend love you more or make your dealings with your parents or in-laws 
any easier. But it must be said that no scripting language or relational database is 
better equipped in these areas. 

But if you're working for a company that sells or otherwise dispenses goods, it is 
a virtual guarantee that someone somewhere is going to be unhappy with what he 
or she has received. When that person complains you are going to want to have a 
place where you can record the problems and the steps required for resolution. 

The problem-tracking application in this chapter can be used for that purpose. 
What we have here is fairly generic, and depending on the goods involved with your 
business, it is likely that you are going to want some fields that apply to your specific 
products. Anyhow, this application should get you moving in the right direction. 



Determining the Scope and 
Goals of the Application 

This problem-tracking system should have aspects that are publicly available and 
others that only someone with the proper authorization can view. It makes sense to 
have a form that users can access over the Web to report their problems. 
Alternatively, someone on the support staff should be able to report problems— for 
example, while taking a phone call from a dissatisfied customer. 



331 



332 



Part IV: Not So Simple Applications 



Once the problem is entered, it should be tracked by the staff. Each action made 
in the attempt to solve the problem should be noted. And the tracking should have 
a public and a private realm— actions that you want the user to see must be differ- 
entiated from those that you do not want the user to see. 

Those with issues should be able to keep track of their problems in two ways. 
They should be e-mailed whenever an update is made to their case that should be 
publicly viewable, and— it should go without saying— a Web page detailing their 
problem should be available. 



What do you need? 



The first thing you need is a form into which people can enter their complaints. 
What we present in Figure 13-1 is fairly generic; remember that for your own appli- 
cations you will probably want to add information regarding specific products. 



3 Bricks & Mortar Problem Tracking: Call Entry - Microsoft Internet Explorer 


HEJB 


J File Edit View Favorites lools Help 


II 


" 


J Address \^\ http://192.1G8.1.1/book/tracking/index.php 


J ^G 





Bricks & Mortar Problem Tracking: Call Entry 

Customer Information 




zi 


First Name: 




Last Name: 




Email Address: 




Street Address: I 




I I 




City: | | 




State/Zip: I | I 




Daytime Phone: J |, | || | ^ | Ihoutc: |9:00 AM 


5:00 PM 




IVening Phone: J [, | || | ^ | Ihoutc: |5:00 PM 


|8:00PM 


Problem Description 


Brief summary of problem: I 


J 


iastaitjij <v m ■ i§ & <Sj i! isiie -HH^wffi^Miiot'S % 3-22 pm 



Figure 13- 1: Problem entry form 



Chapter 13: Problem Tracking System 



333 



Once a problem is entered, there must be a place for the staff to work on the 
complaint. It should include all of the information about the user, the history of the 
complaint, and a place to enter new information. This problem update form would 
look something like the one in Figure 13-2. 



1» hftffBMMHl JJinfffflfflffUmJIHI-lW 



J File Edit View Favorites lools Help 



Add re s s http: ,71 92. 1 88. 1 . 1 /bookVtracking/ediLproblern. php?problem_id=1 3 




Problem: 



skskeis 



"3 



Update Problem 

| In Progrt 



Status: 



^ress T | 
Entry Type: (T pnyate C public 
Notes: 




Save Changes Reset 



History 
Entry Date 

2000-9-26 2:04 PM 
2000-9-26 2:03 PM 
2000-9-244:19 PM 



Entry Type 

public 
public 
public 



Entered By Source Notes 

customer web llloo 

customer program Status set to Opened by customer 

customer program Status set to Opened by customer 



iiastartl ^ q> & ■ ±) ^ <Sj <M\& ±1 3)»VffP^<3>./J^t'tMg 



3:21 PM 



Figure 13-2: Problem update form 



The support staff members need a home, a place where they can log in and see 
both the unassigned tasks and those that are assigned to them and are still open. 
The staff page would look something like the one in Figure 13-3. 

If you want to see if any of your users are hypochondriacs, you can use the user 
history page in Figure 13-4, which lists all problems associated with a user. 



334 



Part IV: Not So Simple Applications 



'3 Bricks & Mortar Problem Tracking: jaygre - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



Address £] http:/AI92.168.1.1/bookAracking/staff.php 



~zi ^Go 



Unowned Calls 



Prohlem ft Date Customer Prohlem 

13 2000-9-26' 2:03 PM iavGandt dkdkeieksi 

2000-9-26" 2 :02 PM iavGandt dkdkeieksi 

2000-9-24 6" :36" PM kkkkkk niieiei j erko 



Source Status 

web Opened 
web Opened 
phone Opened 



2000-9-24 3 : 1 1 PM kdfieeieilaialfeife kkkkkkkkkkkkkkkkkkkkk web 
2000-9-24 5 : 1 1 PM kdfieeieilaialfeife kkkkk web 

2000-9-24:5:08PM iavGandt kkkkkkkkkkkkk web 

2000-9-24 3 :04 PM Iddddd; niieiei loplo web 

2000-8-243:03 PM Iddddd: niieiei loplo web 

2000-9-244:38 PM iavGandt kdiekekeieie web 



Opened 
Opened 
Opened 
Opened 
Opened 
Opened 



Open Calls 



Frohlem ft Date Customer Frohlem Source Status 

4 2000-9-23 1 :48 PM bill bob Status s et to In Pro gre s s by J ay Gre web In Pro gre s s 

4 2000-9-23 1:43 PM bill bob check check web InProgress 



lastarti m <*> & ■ ^ <£ ^i <M\^ ±1 3>»VffO'^<3)^ jot-g^a 



3:22 PM 



Figure 13-3: Staff page 



fa I^BHMHlJWHrJBifflfgmraJWBBf 



J File Edit View Favorites lools Help 



Ad d re s s ^] http: lf\ 92. 1 G8. 1 . 1 /book/tracking/problemjist. php?customer_id=5 




Bricks & Mortar Problem Tracking: Problem List 

Current Calls 



Frohlem n Date Customer Frohlem 


Source Status 


13 2000-9-26 2:03 PM iavGandt dkdkeieksi 


web 


Opened 


12 2000-9-26 2:02 PM iavGandt dkdkeieksi 


web 


Opened 


8 2000-9-245:08 PM iavGandt kklddddddddd± web 


Opened 


5 2000-9-24 4:38 PM iav Gandt kdiekekeieie 


web 


Opened 


Enter a Call 







lastartl <Q & m J3 'A ii M& iA 3)»VffQ'3><3>^ JOt'EMa 



3:23 PM 






Figure 13-4: User history page 



Chapter 13: Problem Tracking System 



335 



What do you need to prevent? 

There is nothing terribly unique about what you need to prevent here. In fact, if you 
decide to put an application like this into production, you might want to take more 
safeguards than we show here. We'll make some suggestions along the way. 



Designing the Database 

As you can see from Figure 13-5 the problems table is at the center of the schema. 



customers 



customerjt 

firstname 

lastname 

address 

address2 

city 

state 

zip 

zip4 

email 

day_area 

dayprefix 

day_suffix 

dayext 

day_ start 

day_end 

eve_area 

eve_prefix 

char(3), 

eveext 

eve_start 

eve end 



status 



statusjd 
status 



problems 



history 



entry types 



problemjd 

customerjd 

statusjd 

owner 

summary 

problem 

entered_by 

sourcejd 

entrydt 

modify_dt 



entryjd 

problemjd 

entryjypejd 

entered _by 

sourcejd 

entrydt 

notes 



entryjypejd 
entry Jype 



username calls 



sourcejd 
source 



problemjd 
entrydt 



tracking admin 




staff 


username 




username 


password 




password 


staff name 




staff name 


active 




active 



Figure 13-5: Tracking system schema 



Each customer can have one or many problems. The history table records the 
steps taken to remedy the problem or problems. 



336 Part IV: Not So Simple Applications 

The status table is a lookup table, containing the possible states of a problem; 
notably "open," "closed," "in processing," etc. The sources table is another lookup 
table that records where the problem was originally recorded. If a user enters a 
complaint over the Web, the sources table will record that; complaints received by 
the support staff might originate from a phone call, e-mail, or flaming arrow. 

The entry_types table notes whether a specific item in the history table should be 
public or private. If it is private it will not be available on the Web page when the 
user comes to view the progress of the problem and an e-mail will not be sent to the 
user when there is an update. The "public" updates will be viewable and the user 
will receive e-mail notification. 

There is something a bit strange in this schema; namely, the calls table. The pur- 
pose of the calls table is to store the most recent entry for each call that has an 
owner. If you look at this schema, you might wonder why this is necessary. After 
all, there are relationships between staff, problems, and history. Therefore, there 
should be a way to join these three tables to get the information you are after. 
Normally you would use sub- selects for something like this. 

select problem.*, history.*, staff.* 

from problem , history, staff 

where probl em. owner = ' $PHP_AUTH_USER' 

and hi story .entry_dt in ( 

select max(n .entry_dt) from history) 

If we were using MySQL version 3.23, we would make use of temporary tables. 

create temporary table calls 
select max(entry_dt ) from history 

Then you could join the problem and history table onto this temporary table. 
However, since this isn't available in version 3.22, which we used when creating 
these applications, we had to use a make shift temporary table. Each time it is 
accessed, the calls table will be emptied out and repopulated with information we 
need to complete the joins. See the staff. php page for the specific SQL we used. 

Now for a couple of notes on this schema and the create statements that follow. 
Depending on how you plan on running your site, you may wish to add a table or 
change a column definition or two. First off, you might want to add a password 
column to the user table, so that you can ensure that the people looking at the site 
should be there. 

Notice that we have a problem_code column in the problems table. However, if you 
will be e-mailing users regarding the status of problems, you may want something 
a little less transparent than the following: http://yoursite.com/tracking/ 
probl ems. php?probl em_id=/. 

If you remember back to Chapter 9, we took some precautions when we ran into 
a similar situation. We didn't want people to be able to access to restricted parts of 
our data by simply guessing at variable names in the URL. Here we will adopt the 



Chapter 13: Problem Tracking System 337 

same technique we used in the survey application, creating a random 8-character 
alpha-numeric string from the md5( ) and uniqueidt ) functions. 

Listing 13-1 shows the create statements for the tables we used in this applica- 
tion. In addition to the create statements, this listing includes some of the default 
data you will need to start the application. Note that if you install this application 
from the CD-ROM you will have a full set of dummy data you can play with. 

Listing 13- 1: Create statements used in the problem tracking system 

CREATE TABLE customers ( 

customer_id int(ll) NOT NULL auto_i increment , 

firstname varchar(40), 

1 astname varchar(40) , 

address varchar(40) , 

address2 varchar(40), 

city varchar(20), 

state char(2) , 

zip varchar(5), 

zip4 varchar(5), 

emai 1 varchar(255) , 

day_area char(3) , 

day„pref ix char(3) , 

day_suffix varchar(4), 

day_ext varchar( 5) , 

day_start varchar(8), 

day_end varchar(8) , 

eve_area char(3) , 

eve_pref ix char (3) , 

eve_suffix varchar(4), 

eve_ext varchar( 5) , 

eve_start varchar(8), 

eve_end varchar(8) , 

PRIMARY KEY ( customers' d ) 
); 

# 

# Table structure Tor table ' entry_types ' 
# 

CREATE TABLE entry_types ( 

entry_type_id tinyint(4) NOT NULL auto_i ncrement , 

entry_type varchar(lO) NOT NULL, 

PRIMARY KEY (entry„type_i d ) 
); 
INSERT INTO entry_types VALUES (1 ,' private' ) ; 



338 Part IV: Not So Simple Applications 

INSERT INTO entry_types VALUES ( 2 , 'public' ) ; 



# 

# Table structure for table 'history' 
# 

CREATE TABLE history ( 

entry_id int(ll) NOT NULL auto_i ncrement , 

problerrMd char(32) NOT NULL, 

entry _type_id tinyint(4) NOT NULL, 

entered_by varchar(20), 

source_id tinyint(4) NOT NULL, 

entry_dt timestamp( 14) , 

notes text, 

PRIMARY KEY (entry_id) , 

KEY hi story_probl em_i d (problerrMd) 
); 

# 

# Table structure for table 'problems' 
# 

CREATE TABLE problems ( 

problerrMd char(32) NOT NULL, 

customer_id int(ll) NOT NULL, 

status_id tinyint(4) NOT NULL, 

owner varchar(20) , 

summary text, 

problem text, 

entered by varchar(20), 

source_id tinyint(4) NOT NULL, 

entry dt datetime, 

modify dt timestamp( 14) , 

problem_code char(8) not null 

PRIMARY KEY ( probl em_i d ) , 

KEY probl em_customer_id ( customer_id) 

KEY probl em_code_key (probl em_code) 



# 

# Table structure for table 'sources' 



Chapter 13: Problem Tracking System 



339 



// 



, 'web' ) ; 




, ' emai 1 ' ) ; 




, ' phone ' ) ; 




, ' i n - s t o r e 


) 


, 'staff ); 




, ' program' 


; 



CREATE TABLE sources ( 

source_id tinyint(4) NOT NULL auto_i ncrement , 

source varchar(lO) NOT NULL, 

PRIMARY KEY (source_id) 
); 

INSERT INTO sources VALUES (1 
INSERT INTO sources VALUES (2 
INSERT INTO sources VALUES (3 
INSERT INTO sources VALUES (4 
INSERT INTO sources VALUES (5 
INSERT INTO sources VALUES (6 

# 

# 

# Table structure for table 'staff 
# 

CREATE TABLE staff ( 

username varchar(20) NOT NULL, 

password varchar(255) NOT NULL, 

Staffjiame varchar(50) NOT NULL, 

active tinyint(4) DEFAULT '1', 

PRIMARY KEY (username) 
); 



# 

# Table structure for table 'status' 



CREATE TABLE status ( 

status_id tinyint(4) NOT NULL auto_i ncrement , 

status varchar(20) NOT NULL, 

PRIMARY KEY (status_id) 
); 



INSERT INTO status VALUES (1, 
INSERT INTO status VALUES (2, 
INSERT INTO status VALUES (3, 
INSERT INTO status VALUES (4, 
# 

# Table structure for table 



'Opened ' ) ; 
' In Progress ' 
'Closed' ) ; 
' Re-opened ' ) ; 



' tracki ng_admi n ' 



340 Part IV: Not So Simple Applications 

CREATE TABLE tracki ng_admi n ( 

username varchar(50) NOT NULL, 

password varchar(255) NOT NULL 
); 

Code Overview 

There's nothing terribly new and exciting in the code presented in this chapter. Some 
of the queries are lengthier than in previous chapters. But it's really nothing major. 

Code Breakdown 

This application makes more liberal use of includes then some of the previous ones 
you have seen. There are a couple of very long forms that could really clutter up a 
page. They have been pushed off to includes. 

Reusable functions 

The base function set, described in Chapter 9, will be used here once again. 

FUNCTIONS FROM /BOOK/TRACKING/FUNCTIONS.PHP 

The first few of these are for convenience. The ones a little further down do some 
pretty heavy and cool work. 

DB_ AUTHENTICATED This calls our standard authentication function, first 
described in Chapter 9. 

function staf f_authenti cate( ) 
{ 

db_authenti cate( "staf f " , "Bricks and Mortar Support"); 



PRINT_ROW() This small function simply makes a call to the tab! e_row( ) func- 
tion in /book/functions/tables. php. It's basically a shortcut so you can print a sim- 
ple two-column row. 

function print_row ( $1 abel =" " , $what=" " ) 
{ 

$1 abel = empty($label ) ? "" : "<b>$l abel : </b>" ; 

print tabl e_row( $1 abel , $what) ; 



You can think of the arguments as names and values. 



Chapter 13: Problem Tracking System 341 

FETCH_STAFF () If you've looked at some of the other applications in this book, 
this type of function should be clear. 

function fetch_staff ($username = "") 
t 

Iresult = fetch_record( "staff " , "username", " ' $username ' " ) ; 

return Iresult; 



It calls to /book/functions/db.php, where the fetch_record( ) creates a query, 
runs it, and then sets the columns returned by the query to global variables. 

FETCH_CUSTOMER () See the previous function for an explanation. 

function fetch_customer ( $customer_id = "") 
t 

Iresult = fetch_record( "customers" , "customer_i d" , 
$customer_i d) ; 

return Iresult; 



FETCH_PROBLEM () This function, which essentially does the same thing as the 
previous three, takes a slightly different form. This is because the fetch_record( ) 
function can only assemble simple queries: one table is all it can manage. However, 
since the set_resui t_vars( ) function is the one that actually assigns the results 
of the query to globals, you can pass the results of a query directly to it. 



function fetch_probl em ( $probl em_i d = "", $probl em_code = "") 
{ 

$query = "select p.* 

, s. status 

, u. source 

from problems p 

left join status s on p.status_id = s.status_id 

left join sources u on p.source_id = u.source_id 

if (! empty ( Iprobl em_id) ) 
{ 

$query .= " where p.problem_id = $problem_id "; 



e I se 



$query .= " where p. probl em_code = ' $probl em_code ' 



342 Part IV: Not So Simple Applications 



Iresult = safe_query ($query ) ; 

if (!$result) ( die("no such problem: <pre>$query</pre>" ) ; ) 
set_resul t_vari ables($result) ; 
return Iresult; 
} 

Here you are gathering all the information associated with a single problemjd. 
For that, you need to join problems on the sources status tables. 

FIND_CUSTOMER() Remember that you would like to enable users to report their 
problems over the Web. In this application, we've decided that while there is a 
numeric primary key for each user, the application should be able to identify the 
user by either a phone number or an e-mail. So when a user enters information, 
you will need to check if someone with an identical e-mail or phone number has 
come along. 

functi on f i nd_customer( $emai 1 =" " 

, $day_area=" " , $day_pref ix=" " , $day_suf f ix=" " 

, $eve_area=" " , $eve_pref ix=" " , $eve_suf f ix=" " 
) 
f 

$where = " " ; 

$sep = "" ; 

if ($day_prefix != "") 



number 



// there must be a prefix for this to be a valid phone 

$where .= " 

(day_area = '$day_area' 

and day_prefix = ' $day_pref ix ' 
and day_suffix = ' $day_suf f ix ' 



// separate each part of the qualification with OR - 

// any part constitutes a valid match. 

$ s e p = " or " ; 
} 

if ($eve_prefix != "") 
{ 

// there must be a prefix for this to be a valid phone 

//number 



Chapter 13: Problem Tracking System 343 



$where .= " 

$sep 

(eve_area = '$eve_area' 

and eve_prefix = ' $eve_pref ix' 
and eve_suffix = ' $eve_suff ix' 

) 

$sep = " or " ; 
} 

if ($ email != "") 
t 

$where .= " 
$sep 
(emai 1 = ' $emai 1 ' ) 



if ($where == "") 
{ 

// nothing to look for 

return FALSE; 



// run a query with the constructed qualification 

// and return the result 

$query = "select * from customers 

where $where 

order by customer_id 

$result = safe_query ( $query ) ; 
return Iresult; 



With this function you will know if the user has an existing record that can be 
used or that might need to be updated. 

Notice the grouping of the portions of the where clause. It is looking for any one 
of three circumstances, each of which must meet a few criteria. If the e-mail, day- 
time phone and evening phone fields are filled in, this function will create a query 
that looks like this: 

select * from customers 
where (day_area = '415' 

and day_prefix = '555' 

and day_suffix = '0410' ) 
or (eve_area = '212' 

and eve_prefix = '555' 



344 



Part IV: Not So Simple Applications 



and eve_suffix = '9999' ) 
or (email = 'jay@trans-city.com') 
order by customer_id 



If you were interested, you could set a cookie to make identifying the user a 
bit easier. 




PRESENT_DUPS() You need to plan for a couple of eventualities: if there are iden- 
tical e-mail addresses or phone numbers, but some other personal information has 
changed, you need to let the user either update the database or discard the data. 
This function spots the redundancy and alerts the user. 

function present_dups ($result) 
{ 

// we have to start the call entry form inside the function - 

// use a global variable to indicate that this was done. 

global $in_form; 

$in_form = 1; 

// start the form 

print start_f orm( "create_cal 1 . php" ) ; 

print paragraph("<b>" 

."We may have found you in our database." 

." Please let us know what you would like to do:" 

."</b>" 

); 

print start_tabl e( ) ; 

// for each customer record in the result set 

while ($row = mysql_fetch_array ( Iresul t ,MYSQL_ASSOC) ) 



// print out the ID value for the record in a radio field, 
// allowing the user to choose only one if more than 
// one is displayed, 
print table_row( 



Chapter 13: Problem Tracking System 345 



radi o_f iel d( 

"customer_i d" 

, $row[ "customer_i d"] 

, "<b>Use this row</b>" 
) 
, "<b> Record #" . $row["customer_id" ] . "</b>" 



) 



// print out the name & address information from this record 
print tabl e_row( " " , $row["f i rstname"] . " " . $row[ "1 astname"] ) ; 
if ( !empty($row["address"] ) ) 
{ 

pri nt tabl e_row( "", $row["ad dress" ]) ; 
} 

if (! empty ( $row["address2"] ) ) 
{ 

pri nt tabl e_row( " " , $row["address2" ] ) ; 
} 

if ( ! empty ( $row["city"] ) || ! empty ( $row["state"] ) 
! empty ( $row[ "zi p"] ) 



print table_row("" 

, $row[ "ci ty" ] . " , " . $row[" state"] . " " 
. $row[ "zi p"] 
); 
} 

if (! empty ( $row["day_pref ix"] ) ) 
{ 

$daycell = "Day: " 

, $row[ "day_area"] 

. $row[ "day_pref ix" ] 

. $row[ "day_suff ix" ] 

if (! empty ( $row["day_ext" ]) ) 
{ 

$daycell .= " " . $row["day_ext" ] ; 
} 
if (! empty ( $row["day_start"] ) ) 



346 Part IV: Not So Simple Applications 



if 



Idaycell .= " from " . $row["day_start"] ; 
} 

if ( lempty ( $row[ "day_end"] ) ) 
{ 

$daycell .= " until " . $row[ "day_end"] ; 
} 
print tabl e_row( " " , $ day eel 1 ) ; 

( lempty ( $row["eve_pref ix"] ) ) 

$evecell = "Eve: " 

, $row["eve_area"] 

$row["eve_pref ix" ] 

$row["eve_suff ix" ] 



i f ( lempty ( $row[ 



sevece I 



i f ( lempty ( $row[ 



Sevece I 



i f ( lempty ( $row[ 



>evece I 



print table_row( 



"eve_ext"] ) ) 

" . $row["eve_ext"] ; 
eve_start" ] ) ) 

from " . $row["eve_start"] ; 
eve_end"] ) ) 

until " . $row[ "eve_end"] ; 
",$evecell); 



if 
f 



( lempty ( $row[ "emai 1 " ] ) ) 
print tabl e_row( " " , $ row ["emai 1 " ] ) ; 



// print out a checkbox field allowing the user to 
// indicate that this record should be overwritten 
// with the information from the form, 
print table_row("" 

, checkbox_f iel d( 
"override" 



Chapter 13: Problem Tracking System 347 



>row["customer_i d"] 

'<b>Override this entry with the new" 
. " information in the form below. </b>" 



// print out a checkbox field allowing the user to 

// indicate that this record should be merged 

// with the information from the form and the result 

// written back to the database. 

print table_row("" 

, checkbox_f i el d( 
"merge" 

, $row[ "customer_i d"] 
, "<b>Merge this entry with the new" 

." information in the form below. </b>" 
) 



print end_tabl e( ) ; 

// print out a final radio field indicating that, rather than 
// using any of the records found in the database, a new record 
// should be created. 
print paragraph^ radi o_f iel d( 

"add_as_new" 

, "yes" 

, "Create a new record with the information in the form 
bel ow. " 
)); 



All the duplicate rows are printed, and the user can choose what to do with the 
data. You can see this in action if you go to the index. php page of this application 
and attempt to enter two different tickets with, say, the same phone number. Figure 
13-6 gives an example. 



348 



Part IV: Not So Simple Applications 



W Bricks & Mortar Problem Tracking: Call Creation - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



Address |*S] http:/AI 92.168.1. 1/bookAracking/create_call.php 



~zi t^Go 



We may have found you in our database. Please let us knowwhat you would like to do: 



O Use this i 



v Record #7 

Sarah dklwiei 

273 third 

SanFrancisco, ca94110 

Day: 999 999-9999 from 9:00 AM until 5:00 PM 

Eve: 777 777-7777 from 5:00 PM until S:00 PM 

j do e@no domain, c om 

I Override this entry with the new information in the form below. 

I - Merge this entry with the new information in the form below. 



* Cre ate a new re c ord with the information in the form b elo w. 



Customer Information 
First Name: 

Last Name: 

Email Address: 



| Sarah 



|dklwie 



" 



lirtnRfoJnnrJn 



main mm 



:flst art | ®*>&m^'A^i jg[gj -rj 3)»VffQ^-i --:<§Pt'g g B 



3:26 PM 



Figure 13- 6: Form for updating customer information 

HISTORY_ENTRY() When a staff member enters an update on a problem, the step 
is stored in the history table. If the entry is "public" the user will be e-mailed with 
the update; if not, there will be no e-mail. 

Notice the interesting query. Here the insert contains a select statement. The only 
thing this select is actually getting is the source_id related to the variable $source. 
All the rest of the insert information comes from variables. 



function hi story_entny 

( $probl em_i d=" " , $entry_type_i d=" " , $entered_by=" " 

,$source="" ,$notes="" 
) 
{ 

if (empty ( $probl em_i d) ) { return FALSE; } 

if (empty ( $entered_by) ) { $entered_by = "customer"; ) 

// create a record in the history table, getting the ID value 
// of the source from the sources table. 
$query = "insert into history 



(probl em_id ,entry_type_i d , entered_by , source_id .notes ) 

select '$problem'_id, $entry_type_id , ' $entered_by ' , 



Chapter 13: Problem Tracking System 349 



source_id, '$notes' 
from sources where source = '$source' 

$result = safe_query ( $query ) ; 

if ($result) 

{ 

// get the ID value of the new history record 

// (automatically assigned by MySQL). 

$entry_id = mysql_i nsert_i d( ) ; 

// get the email address of 

// the customer who opened this call 

// if this was a public history 

// entry, and if the email address 

// is not empty 

$cresult = safe_query ( "sel ect c. email 

from problems p, customers c, history h, entry_types et 

where h.entry_id = $entry_id 

and et .entry_type_i d = h . entry_type_id 

and et .entry_type = 'public' 

and h.problem_id = p.problem_id 

and p . customer_id = c . customer_id 

and c. email != '' and c. email is not null 

if ($cresult && mysql_num_rows( Icresul t ) ) 
{ 

// we have a valid email address - use it to 

// notify the customer that the call record 

// has been updated. 

1 i st($emai 1 ) = mysql_fetch_array ( $cresul t ) ; 

noti fy_customer( ' $probl em_i d ' ,$email ,$notes) ; 



// return the result of the creation of the new history record 
return Iresult; 



If the update is public, the notify_customer( ) function is run. 

NOTIFY_CUSTOMER() This function constructs an e-mail and sends it. 

function noti fy_customer ( $probl em_id=" " , $email="", $notes="'\ 
$probl em_code=" " ) 



350 Part IV: Not So Simple Applications 

// the Apache global variable $SERVER_NAME is the name 
// of the server we're running on, minus any port number, 
global ISERVER_NAME; 

// remove any HTML tags and backslashes from $notes. 
Inotes = stri psl ashes (cl eanup_text( $notes )) ; 

if (empty ( $probl em_code) ) 
{ 

Iresult = safe_query ( "sel ect problem_code from 
probl ems 

where problem_id = $problem_id 
"); 

Iprobl em_code = mysql_resul t( $resul t , 0) ; 
if (empty ( $probl em_code) ) 
{ 

Iprobl em_code = create_probl em_code( ) ; 
safe_query( "update problems 

set probl em_code = ' Iprobl em_code ' 
where probl em_id = Iprobl em_i d 
"); 
) 
// build an absolute URL calling the probl em_status .php page 
// to check on this problem 
lproblem_url = 
regul ar_url ( "probl em_status .php?probl em_code=$probl em_code" ) ; 

// set the body of the email 
Imsgtext = <<<E0Q 

Problem Update: 

Inotes 

You can check the current status of this problem at 

Iprobl em_url 

Thanks for your patience. 

EOQ; 

// set the headers of the email 

Iheaders = "From: webmaster®" . ISERVER_NAME . "\n" 

."Reply -To: webmaster®" . ISERVER_NAME. "\n" 

."X-Mailer: PHP/".phpversion() 



Chapter 13: Problem Tracking System 



351 



// send the email 
return mai 1 ($emai 1 



"Problem Update", $msgtext, $headers); 




PHP will have to be able to find sendmail or another SMTP-compliant mail 
server in order for this to work. Check your php.ini file is you're having 
problems. 



STATUS_CHANGE() The status of a problem is going to be something like "open," 
"closed," or "pending." If it changes you are going to want to mark the exact 
change and record something like "status changed to closed by J ohn." The change 
should be recorded in the history table. 

f uncti on status_change( Iprobl em_id=" " 
, $new_status_i d=" " 
, $ol d_status_i d=" " 
, $entered_by="customer" 



if (empty ( Iprobl em_id) 
| $ol d_status_id = 
) 



| empty($new_status_id; 
■ $new_status_i d 



return ; 



if (empty ( $entered_by) ) ( $entered_by = "customer"; ) 

// get the ID of the entry_type 'public', and construct 

// a string containing the new status value and either 

// the real name of the staff member who made the change, 

// or the value of $entered_by if no matching staff 

// member is found, for example, if the staff member Joe 1 

// closes a call, the notes field will be set to 

// "Status set to Closed by Joe Blow", if a customer 

// re-opens a call, notes will be set to 

// "Status set to Re-opened by customer". 



// all of this depends on the value in $new_status_id 
// being a valid ID of a record in the status table. 
$query = "select et .entry_type_id 



352 Part IV: Not So Simple Applications 



, concat( ' Status set to ' 
, ns. status 
, ' by ' 

, ifnul 1 (t . staff_name , ' $entered_by ' ) 
) as notes 

from entry_types et, status ns 
left join staff t on t.username = ' $entered_by ' 
where et .entry_type = 'public' 
and ns.status_id = $new_status_i d 

Iresult = safe_query ( $query ) ; 

if (Iresult) 

{ 

// $new_status_i d is a valid status ID - use the 
// hi story_entry ( ) to make an entry in the history table 
// recording the status change, and send email notifiying 
// the user. 

1 i st( $entry_type_id , $notes) = mysql_fetch_array ( $resul t ) ; 
hi story_entry ( $probl em_i d , $entry_type_i d , $entered_by 
, ' program' , $notes 



DISPLAY_CALL_LIST() This is another function of convenience. It prints the 
results of a query along with a header row. 



function displ ay_cal 1_1 i st( $query=" " , $subti tl e="Cal 1 List" 
, $scri pt="edi t_probl em.php" 



if (Iquery == "") ( return; ) 
Iresult = safe_query ( $query ) ; 
if ( ! $resul t ) ( return ; ) 

Icalls = 0; 

while ($row = mysql_fetch_array ( $resul t ) ) 

{ 

if (Icalls == 0) 
{ 

// we want to print out the table header only once, 
// and only if there is at least one row - 
// do it when processing the first row 
// of the result set. 



Chapter 13: Problem Tracking System 



353 



print subti tl e( Isubti tl e) ; 



pri nt start_tabl e( ) ; 



print table_row( 

"<b>Problem #</b>" 
"<b>Date</b>" 
"<b>Customer</b>' 
"<b>Problem</b>" 
"<b>Source</b>" 
"<b>Status</b>" 



$calls++; 

// print out information about the call, including 
// a link to the given script for updating its 
// status and history, 
pri nt tabl e_row( $row["probl em_i d" ] 

, $row["entry_dt" ] 

, anchor_tag( $script."?probl em_id=" 
. $row[ "probl em_id" ] 
, $row[ "f i rstname" ] . " " . $row["l astname"] 

) 

, $row[ "summary"] 

, $ row ["source"] 

, $row[" status"] 



if (Icalls > o: 



// there was at least one cal 

/ / close it. 

pri nt end_tabl e( ) ; 



so the table was opened - 



CREATE_PROBLEM_CODE() This function creates a unique and highly random 
8-character alphanumeric code. 



function create_probl em_code( ) 
{ 

return substr(md5(uniqid(rand())),0,8); 



354 Part IV: Not So Simple Applications 

Scripts 

Here are the pages that are actually called by URLs and the includes. 

CALL_ENTRY.PHP 

This page does little but call the call_entry_form.php, which is discussed next. 

Remember, it is possible that this form will be accessed by a staff member who is 
logged in. Note that staff members should start at the staff. php page to log in. Or 
again, you may want to work with cookies to determine staff members. 

If either a staff member or a user is identified, all the information regarding the 
user is set to globals with the appropriate fetch function. 



include "header. php"; 

if ( $use_staf f_name == "yes") 
{ 

db_authenti cate( "staff " , "Bricks and Mortar Support"); 

fetch_staff($PHP_AUTH_USER); 
} 

$page_title = $defaul t_page_ti tl e. " : Call Entry"; 
include "start_page .php" ; 

if ( lempty ( $customer_id) ) 
{ 

// a customer ID value was submitted - get the customer's 

// contact information from the database. 

fetch_customer( $customer_id) ; 



// call the script that actually displays the call entry form, 
i ncl ude "cal l_entry_f orm. php" ; 

include "end_page. php" ; 



CALL_ENTRY_FORM.PHP 

Mostly this form makes calls to the functions in your /book/functions/ folder. It 
prints the form shown in Figure 13-1 and determines the default information in the 
form. The call_entry.php page will include this page. 

The form will be submitted to the create_call.php page, which is discussed next. 

if ( ! $i n_form) 



Chapter 13: Problem Tracking System 



355 



// if the user has submitted the form once and existing 

// contact information was found for the customer, 

// the present_dups( ) function (defined in functions.php) 

// will have already started the form, in order to 

// display the contact records to the user, in this case, 

// the function will set the global variable $in_form to 1. 

// if $in_form is not set, begin the form here. 

print start_form( "create_cal 1 . php" ) ; 



print start_tabl e( ) ; 

// print out the customer's contact information, 
print tabl e_row(tabl e_cel 1 ( "<b>Customer 
Information</b>",array("colspan"=>2))); 



pri nt_row( " Fi rst Name" , text_f i el d( "f i rstname" , "$f i rstname" ,40) 
pri nt_row( "Last Name" , text_f i eld("lastname","$lastname",40)); 
pri nt_row( "Emai 1 Address" , text_f i el d( "emai 1 " , "$emai 1 " ,40) ) ; 
pri nt_row( "Street Address", text_f i el d(" address ","$address", 40) 
pri nt_row( " " , text_f i eld("address2","$address2",40, 40)) ; 
pri nt_row( "Ci ty" , text_f i el d( "ci ty " , "$ci ty " ,40 , 40) ) ; 
pri nt_row( "State/Zi p" , text_f i eld("state","$state",2, 2) 

. " & n b s p ; " 

. text_f iel d( "zip" , "$zip", 5, 5) 



) 



. text_f iel d( "zii 



"$zip4", 4, 4) 



// by default, daytime phone numbers cover the hours of 9 AM to 5 

PM. 

if (empty($day_start)) ( $day_start = "9:00 AM"; ) 

if (empty($day_end)) { $day_end = "5:00 PM"; ( 



pri nt_row( "Daytime Phone", "(" 

. text_f i el d( "day_area" , "$day_area" ,3,3) 



text_f i el d ( "day_pref i x" , " $day_pref i x" ,3,3) 

text_f i el d( "day_suf f ix" , "$day_suff ix" ,4,4) 

"  " 

"<b>Ext:</b>\n" 

text_f iel d( "day_ext" , "$day_ext" ,5,5) 

"  " 

"<b>Hours:</b>\n" 

text_f i el d( "day_start" , "$day_start" ,8,8) 



356 Part IV: Not So Simple Applications 



. text_f i el d( "day_end" , "$day_end" , 



); 



// by default, evening phone numbers cover the hours of 5 PM to 8 

PM. 

if (empty($eve_start) ) ( $eve_start = "5:00 PM"; ) 

if (empty($eve_end) ) ( $eve_end = "8:00 PM"; ) 

pri nt_row( " Eveni ng Phone", "(" 

. text_f i el d( "eve_area" , "$eve_area" ,3,3) 

text_f i el d( "eve_pref ix" , "$eve_pref ix" ,3,3) 
text_f i el d( "eve_suff ix" , "$eve_suff ix" ,4,4) 
"&nbsp ; " 
"<b>Ext:</b>\n" 

text_f i el d( "eve_ext" , "$eve_ext" ,5,5) 
"&nbsp ; " 

"<b>Hours:</b>\n" 

text_field("eve_start" ,"5:00 PM",8,8) 
text_field("eve_end" ,"8:00 PM" ,8,8) 
); 



// spacer row to separate the two main areas of the form - we have 
// to use a blank space ( ) to fill out the table cell, or it 
// won ' t show up (much ) . 
print tabl e_row( tabl e_cel l(" ",array("colspan"=>2))); 

// print out fields for describing the problem, 
print tabl e_row( tabl e_cel 1 ( "<b>Probl em 
Description</b>",array("colspan"=>2))); 

if ( lempty ( $use_staff_name) ) 
{ 

// if a staff member is entering a problem record, record 
// the fact in hidden fields, 
pri nt_row( " Entered by", $username 

. hi dden_f i el d( "entered_by" , lusername) 
. hi dden_f i el d( "use_staff_name" , $use_staf f_name) 
); 



// allow the staff member to indicate the original source of the 
// problem report, 
pri nt_row( "Source" 

, db_sel ect_f i el d( "source_i d" , "sources", "source_id", 
"source") 



Chapter 13: Problem Tracking System 357 



pri nt_row( "Bri ef summary of problem" 

, textarea_fi el d( "summary" , $summary, 40, 2) 



pri nt_row( "Compl ete Problem Description" 

, textarea_f i el d( "probl em" , Iprobl em, 40, 15) 
); 



print end_tabl e( ) ; 



print paragraphs 

submi t_fi el d( "submi t" , "Submit Problem Report' 

, reset_f i el d( ) 
); 



print end_form( ) ; 



CREATE, CALL.PHP 

This page is long, if not terribly complicated. I will discuss interesting parts as they 
present themselves. When you're looking at it, it's easiest to know the variety of 
actions it can accomplish. Here's an overview of the logic in the page: 

customer or staff comes to call_entry 
if customer_id is available 
fetch that record 
display it in the form 
el se 

display blank form 

if email or phone number have no match in db 

create new customer address 
el se 

if more than one records match 
present records to customer 
al 1 ow customer to: 

- create new record with new info 

- use an existing record 

- override an existing record with new info 

- merge new info with an existing record; 



358 Part IV: Not So Simple Applications 

where existing has values and new 
does not, existing will be used 
- mark one of the records as a duplicate 
to be purged; calls tied to it 
will be linked to the record 
used for this call 
present new info in form to allow for updating 
el se 

if no new info overrides existing record 

use existing record 
el se 

present record to customer 
al 1 ow customer to: 

- create new record with new info 

- use existing record 

- override existing record with new info 

- merge new info with existing record; 
where existing has values and new 
does not, existing will be used 

present new info in form to allow for updating 

Armed with this overview, you should be able to read through the source of this 
page, which of course is on the CD-ROM , without much difficulty. 

STAFF.PHP 

This is where you expect the staff members to log into the application. Note the use 

of the staff_authenticate( ) function, which calls the authenti cate( ) function 

you've been using throughout the book. Before a staff member can log in, he or she 
must enter a valid password and username. 

The page is going to show two lists of queries, a list of calls owned by the cur- 
rently logged-in staff member, and a list of unowned calls, probably stuff that has 
been entered over the Web. 

include " header. php"; 
staff_authenti cate( ) ; 

fetch_staff($PHP_AUTH_USER); 

$page_title = $defaul t_page_ti tl e. " : $PHP_AUTH_USER" ; 
include "start_page .php" ; 

// first, get a list of all open unowned calls and display them, 
// in the hopes that the user might grab one. 
Iquery = "select c . customer_id , c.firstname, c.lastname 
, p.problem_id 



Chapter 13: Problem Tracking System 



359 



, date_format(p.entry_dt, '%Y-%c-%e %l:%i %p') as entry_dt 

, p.source_id, p. summary, p.entered_by 

, s. status 

, so. source 
, p.entry_dt as real_entry_dt 

from customers c, problems p, status s, sources so 
where p.status_id = s.status_id and s. status = 'Opened' 

and p . customer_id = c . customer_id 

and p.source_id = so.source_id 
order by real_entry_dt desc 

di spl ay_cal 1_1 i st( $query , "Unowned Calls"); 

The above query retrieves a list of calls that are marked "Opened." Note that 
"Opened" will be the status only for unowned calls. As soon someone performs an 
action, the status will change. The displ ay_cal l_l i st() function prints the results of 
the query, if there are any. 

If you remember back to the Defining the Database section, we had to use some 
trickery to get a listing of the most recent entries to calls that belong to a specific 
user. We need to create that make shift table we talked about, but before we do that, 
we need to clear out any old data that may be in there. 

safe_query ( "del ete from calls where username = ' $PHP_AUTH_USER' " ) ; 

Then we load up the temporary table with data that is needed for the join. 

safe_query ( "i nsert into calls (username, problem_id, entry_dt) 
select p. owner 

, h.problem_id 

, max( h .entry_dt) as entry_dt 
from problems p, history h 
where p.problem_id = h.problem_id 
and p. owner = ' $PHP_AUTH_USER' 
group by p.problem_id 
"); 

Finally, we perform the following on the newly loaded table. 



$query = "select c . customer_id , c.firstname, c.lastname 
p. probl em_id , p.source_id, p.entered_by 
date_format(h.entry_dt, '%Y-%c-%e %l:%i %p') as entry_dt 
h. notes as summary 
s . status 
so. source 



360 Part IV: Not So Simple Applications 

, h.entry_dt as real_entry_dt 
from customers c, problems p, status s, sources so 

, history h, calls m 
where p.status_id = s.status_id and s. status != 'Closed' 

and p. customer_i d = c. customer_i d 

and p.source_id = so.source_id 

and p.problem_id = h.problem_id 

and p. owner = m.username 

and h.entry_dt = m.entry_dt 

and h.problem_id = m.problem_id 

and m.username = ' $PHP_AUTH_USER' 
order by real_entry_dt desc 

di spl ay_cal 1_1 i st( $query , "Open Calls"); 

// print out a link to allow the user to create a new call 

print paragraph ( anchor_tag( "cal l_entry . php?use_staff_name=yes" ."Call 

Entry" )) ; 

include "end_page. php" ; 

EDIT_ PROBLEM. PHP 

This is the final file in this application. It is fairly brief and the source code on the 
CD-ROM should be readable. 



Summary 



The application presented in this chapter is very useful, since just about every infor- 
mation services department at every company will have some sort of system to track 
user complaints. As we stated at the beginning of this chapter, the problem tracking 
system presented here is fairly generic. However, it can definitely be the basis for a 
more detailed application that you'd custom design for use in the workplace. 



Chapter 14 

Shopping Cart 

IN THIS CHAPTER 

♦ Creating a secure site 

♦ Working with PHP sessions 

♦ Communicating with a credit-card authorization service 



OK, friends. This is it, the final application in this book. I don't know about you, 
but I'm a little weepy. Sure, there were hard times. But all in all I feel great about 
what we've accomplished, and I hope you do too. 

But before we start reminiscing, there's some more work to be done. You are 
going to learn what you need to create a shopping cart application using PHP and 
MySQL. But unlike with the other applications in this book, it's really impossible 
to talk about what you need for this application without delving into some other 
topics. In addition to understanding the schema and the PHP code, you'll need to 
have a basic understanding of know how to maintain state between pages. (If you 
don't know what that means, don't worry, I'll get to it momentarily.) Also, you will 
need to know how to securely process credit-card transactions. 



Don't read another sentence if you have not read through Chapter 10. You 
must understand how the catalog works before you can take this on. For rea- 
sons that shouldn't be too tough to understand, we built the shopping cart 
atop the catalog. 



Determining the Scope and 
Goals of the Application 

Anyone familiar with the Web knows what a shopping cart does. But it will be a bit 
easier to understand what the code for this application is doing if I explicitly state 
some of the goals of the shopping cart. 




361 



362 



Part IV: Not So Simple Applications 



First, the application is going to have to display your wares; for this you will 
reuse the code from Chapter 10. Further, users will have to be able to choose items 
that they want to buy. Obvious, I know. Note what must happen after a user chooses 
an item: the exact item must be noted, and the user should have the opportunity to 
continue shopping. The server must remember what has been ordered. As the user 
continues to browse, the server must keep track of the user and allow him or her to 
check out with his or her requested items. 

This requires you to use some method for maintaining state. That is, the Web 
server will need to remember who the user is as he/she moves from page to page. 
Now, if you remember back to the Introduction, the Web and the HTTP protocol 
that the Web makes use of is stateless. That is, after responding to an HTTP request, 
the server completely and totally forgets what it served to whom. The server takes 
care of requested information serially —one at a time as requests come in. There is 
no persistence, no connection that lasts after a page has been served. 

In order to give your site memory, so that, in this case, the cart can remember 
who ordered what, some information that identifies the user must be sent with each 
page request. On the Web there are exactly three ways to store this information: 

♦ You can set a cookie, and then each time a request is made, the information 
stored in the cookie will be sent to the server. Note that the browser stores 
the cookie information in a small text file and sends the information to the 
server with each request. 




The setcooki e( ) function is covered in Chapter 6. 



♦ You can place hidden form elements on every page, and then design your 
pages so that the navigation is done through form submit buttons. 

♦ Or you can tack on some sort of unique identifier to the querystring, so 
that with each page request the URL identifies the user. 



But, as they have done with everything else in PHP, the developers have made 
maintaining state relatively painless. As with ColdFusion and ASP, they have 
included a facility for sessions. (Only sessions in PHP are much better.) If you are 
unfamiliar with the term, sessions automate the process of tracking users during a 
visit. They will be discussed in further detail later in the chapter. 

The other major challenge in this application is to securely gather user information 
from the user— specifically data that will allow for the authorization of credit-card 



Chapter 14: Shopping Cart 



363 



purchases. Information will need to be gathered over a connection that is relatively 
secure, and then that information will need to be verified by a qualified institution. 
This is going to require some new configurations and the use of some custom tools. 



What do you need? 



Since you are building this application atop the catalogue, much of the code and 
information should be very familiar. The one notable thing that is going to be added 
to every page is a button that lets people go directly to the checkout. Figure 14-1 
shows an example. 



'SBaqO'Stuff Catalog - Microsoft Internet Explorer 



File Edit View Favorites lools Help 



Address |@ http7/www. madfish. com: 8080/ book/carMndex. php 



ED 



~3 <^ G ° 



> „ -► „ & © a 

Back Stop Refresh Home 



m. a 

Search Favorites History 



BagO' Stuff Catalog 



Here atBag'O'Stuff, we've got so much stuff, we don't know what to do with it. So buy some and help us clear 
out some space. 

What" ve We Got? 

- T-Shirts 

■ Geometric Shapes 



Checkout | 



J 



lastarti m <p rij ■ jg ^ <gj _gj[£i -rj 3 $' :& & € $>0'^m<2>s*>t'% 



Figure 14- 1: Category page with checkout button 



On the pages that list the items, there must be a set of forms that will enable people 
to indicate the items they would like to purchase. Figure 14-2 shows a page that will 
allow people to add items to their shopping carts by pressing the Order! button. 

Then there must be a page that lists all of the items currently in the cart. This 
page should enable people to add or subtract quantities of an item, or remove items 
entirely. The page in Figure 14-3 should do the trick. 



364 



Part IV: Not So Simple Applications 



-' Bag'O'Stuff: T-Shirts: Plain - Microsoft Internet Explorer 



File Edit View Favorites lools Help 



Address |#] http7/www.madfish.com:8080/book/cart/displav.php?categorv_id=UproducLicl=1 



Back Stop Refresh Home Search Favorites History 



Mail Print 



~ZJ <^ G ° 



Edit 



Bag'O'Stuff : T-Shirts : Plain 

Old standby 



Style 

black 



Image 



Available in: 



:JB Start 



Style Order 



Sub Style Order 



S 10.00 Qty[[ 
M 10.00 QtyQ 



Order! 



L 10.00 QtyQ 
XL 10.00 QtyfT 



Order! 



Order! 



Order! 



S 10.00 Qtyfi 0rder! | 



@V®m^&^i UjjUi TJll3 1 a.)°'gg'^<g'^^v-'g 



11:12AM 



Figure 14- 2: Item pages with order buttons 



-3 Bag'O'Stuff: Shopping Cart - Microsoft Internet Explorer 



File Edit View Favorites lools Help 



Address |§ http: //madfish. corn: 8080/book/cart/cart. php 



<P T =► T © [3 (S 

Back Stop Refresh Home 



© a 

Search Favorites History 



~TJ ^ Go 



Bag'O'Stuff : Shopping Cart 



|ltem 


Quantity Price Total |Remove? 


T-Shirts- Plain- black- L: 


□ 


10.00 


$10.00 


[Z remove 




Grind Total: 


1 |$10JOO 



Recalculate 



Continue Shopping 



Check Out 



IrflStartl m*>&m±l&<i i |]jj[fj TJ||JaU»'ff^%J<a^^ti3'% 11:16AM | 



Figure 14- 3: The cart page 



Chapter 14: Shopping Cart 



365 



Then, toward the end of the process, there will be a page that gathers user informa- 
tion and preferences. Depending on your specific needs, you may require something 
more complex than what is shown in this chapter. But at least the fields shown in 
Figure 14-4 are a good start. This page should indicate errors if the information is false 
or if the authorization of the credit card is rejected by the processing agency. 



'a Check Out - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



Address |g] https://madfish.com: 444/book/cart/checkout.php?sessid=7cc2358cf2a38f67b0e8644a996dc991 






/, ! ] ! = 



T 



"3 



r 



Shipping Method Per Order Per Item Total for This Order 

© US Mail 12.00 $ - 50 $ 2:50 

O UPS 2nd Day ^°° $ 10 ° ^ 00 

O UPS Ned Day $ 1000 $ 10a « 100 

Credit Card Info 

Credit Card: (T American Express C Diners Club C Discover <~ MasterCard C Visa 
Number: j 

Eqiires: J1 J^J| 2000 _^J 

ORDER NOW! | 



d 



11:18 AM 



Figure 14-4: Checkout page 



Finally, there should be a receipt— a page that confirms the order and tells the 
user what the order number is. 



Some information that might be important to you is not included with the 
code here. For instance, there are no administrative pages for adding or 
deleting shipping methods. At this point, you've probably seen enough 
admin pages to know how to create one for yourself. 




What do you need to prevent? 

There are essentially two things you need to be careful about here. The first is making 
sure you can track your users from page to page. The second is keeping credit-card 
numbers and other personal information away from prying eyes. 



366 



Part IV: Not So Simple Applications 



The Data 



The database used here will be added to the catalog database. Information about 
goods will still come from the tables reviewed there, while information on orders 
will be stored in the tables shown here. 

The data schema here, represented in Figure 14-5, offers few surprises. 



addresses 

addressjd 

userjd 

address 

address2 

city 

state 

zip 

phone 



status 



statusjd 
status 



users 

userjd 
email 
firstname 
lastname 



orders 



shipping 



shippingid 

shipping 

per_order 

perjtem 



orderjd 

userjd 

addressjd 

statusjd 

total_price 

shippingjd 

shipcost 

cc_number 

cc_exp_yr 

cc_exp_mon 

ccJ:ype_code 

create dt 



items 

orderjd 

itemjd 

productjd 

stylejd 

substylejd 

qty 

price 

total_price 



cc_ types 

ccJype_code 
ccjype 



Figure 14-5: Cart schema 

A couple of things here are worth noting. First notice how the order is really the 
center of the works. Every table, in one way or another, is related to the order. The 
order table stores a userjd, addressjd, and all the needed payment information. 
Notice that the order table has a one-to-many relationship with the items table. 
That's because each order can have many items, which makes sense. 




Think about whether or not you want to store credit-card numbers in your 
database. We have included a column for the credit card number, but that 
doesn't mean you should use it. First consider if your box is secure enough. If 
you're notsu re oftheanswer,theanswerisno.lf you are using ashared server, 
maybe a secure server offered by your ISP, you should consider it unsafe for 
storing credit card numbers. Other people will have access to that box and 



Chapter 14: Shopping Cart 367 



they will be able to see what you've written to the database. Keep in mind that 
there is no requirement that you store the credit-card numbers anywhere. You 
can simply validate the number and then drop it from memory. If it is never 
written to a disk, it will be very hard for anyone to steal. 



Notice that user and address information is separated. This is so you can enable 
a single user (who will identify him- or herself by an e-mail address) to supply two 
addresses— maybe work and home. 

The shipping table will store options you supply for moving your goods. It might 
have options for UPS, USPS, and Fed Ex, if you choose. I'll talk a little more about 
these options in the "Code Overview" section. 

The status table will enable you to note if the order is backordered, shipped, or 
cancelled, or has any other status that might come up in your shop. 

And the cc_ types table stores credit cards that you're willing to accept. 

# Table structure for table 'addresses' 
# 

CREATE TABLE addresses ( 

address_id int(ll) NOT NULL auto_i ncrement , 

user_id int(ll) NOT NULL, 

address varchar(40) , 

address2 varchar(40), 

city varchar(40), 

state char(2) , 

zip varchar(lO), 

phone varchar(20) , 

PRIMARY KEY (addressed), 

KEY address_user_key (user_id) 



Table structure for table 'cc_types' 



CREATE TABLE cc_types ( 

cc_type_code char(3) NOT NULL, 
cc_type varcharOO) NOT NULL 



# 

# Table structure for table 'orders' 



368 



Part IV: Not So Simple Applications 



ULL, 
0.00' NOT NULL, 



CREATE TABLE orders ( 

order_id int(ll) NOT NULL auto_i rcrement , 

user_id int(ll) NOT NULL, 

addressed int(ll) NOT NULL, 

status_id tinyint(4) NOT NULL, 

total_price decimal ( 10 , 2) DEFAULT '0.00' NOT 

shipping_id tinyint(4) NOT NULL, 

ship_cost decimal (10,2) DEFAULT 

cc_number varcharOO) NOT NULL, 

cc_exp_yr int(ll) NOT NULL, 

cc_exp_mon tinyint(4) NOT NULL, 

cc_type_code char(3) NOT NULL, 

create_dt timestamp( 14) , 

PRIMARY KEY (order_id) , 

KEY order_user_key (user_id) 



Table structure Tor table 'shipping' 



CREATE TABLE shipping ( 

shipping_id tinyint(4) NOT NULL auto_i ncrement , 
shipping varchar(20) NOT NULL, 

per_order decimal ( 10 , 2) DEFAULT '0.00' NOT NULL, 
per_item decimal ( 10 ,2) DEFAULT '0.00' NOT NULL, 
PRIMARY KEY ( shi ppi ng_i d ) 



# Table structure Tor table 'status' 
# 

CREATE TABLE status ( 

status_id tinyint(4) NOT NULL auto_i ncrement , 
status varchar(20) NOT NULL, 
PRIMARY KEY (status_id) 



# 

# Table structure Tor table 'users' 



Chapter 14: Shopping Cart 369 



CREATE TABLE users ( 

user_id int(ll) NOT NULL auto_i increment , 

email varchar(255) NOT NULL, 

firstname varchar(40), 

1 astname varchar(40) , 

PRIMARY KEY (user_id) , 

UNIQUE user_emai l_key (email) 
); 



Configuration Overview 

This application is specialized enough to require its own configuration. All of the 
challenges discussed earlier (maintaining state, secure gathering of credit-card 
information, and processing of credit cards) not only require specialized code, they 
require some unique installation options. 

Configuring for encryption and security 

If you have a lot of experience with Apache and its related tools, this may not be too 
big a deal, or if you are using an ISP and don't have the authority to install programs 
on a box, then you won't need to worry about the specialized installation necessary 
to work with e-commerce. 

But in any case, you should have an idea of the tools you'll need to get all of this 
working. First I will cover the basic theories behind encryption and Web security. I 
will then cover some of the mandatory tools for your Apache installation. Finally, 
I will cover some of the options for maintaining state and processing credit card 
transactions offered in PHP. 

ENCRYPTION AND SECURITY THEORY 

One of the best things about working around the Web is having first-hand knowl- 
edge of the work done by people far, far smarter than myself. Some of the most 
intense, complex and brain-intensive work being done is in the realm of security. 
This is algorithm-heavy stuff, and to really understand how the protocols work, 
you need to know quite a bit of math. Luckily, you don't need to have an advanced 
degree to understand the theories; and putting the stuff into practice really isn't 
too bad. 

PUBLIC- KEY/PRIVATE- KEY ENCRYPTION Machines on the Web make use of a 
Public- key/Private- key security scheme. Basically this means that computers that 
wish to communicate using encrypted data must have two keys to encrypt and 
decrypt data. First there is the Public key. As the name suggests the Public key is 
not hidden. It is available to all those you wish to communicate with. So everybody 



370 Part IV: Not So Simple Applications 

out there who wishes to communicate with you securely will have a copy of your 
Public key. 

You might think that this is potentially dangerous. After all, everyone has access 
to your Public key, and thus they'll understand how you encrypted your data. But 
actually, it's just fine, because the messages can only be decrypted by the Private 
key. The Private key is kept ... well .. . private. No one else has access to it. 

So, for example, say you are going process a credit card with a bank. You will 
have access to the bank's Public key, with which you will encrypt the information. 
But because of the complex algorithms involved, only the Private key held by the 
bank can decrypt the data. 

CERTIFICATES Even with the Public key/Private key safeguards, the banks will 
have one major concern: that the messages they are getting are not from the 
sources they appear to be from. That is, if you are running sofamegastore.com, the 
bank needs to make sure that the request for credit-card authorization for that 
loveseat is actually from Sofa Megastore, not someone who is pretending to be Sofa 
Megastore. This requires a third party. 

The encrypted messages that you send and receive will have a signature of sorts, 
but that signature must be verified. For this reason, organizations that wish to 
communicate over the Web make use of organizations that distribute certificates 
that verify the sender of a message. So it should make sense that you need to go to 
one of these organizations to get your Public and Private keys. 



Probably the best-known organization involved in security certificates is 
VeriSign. You can find out about their offerings at this site: http:// 
www. verisign.com/products/site/ss/index. html. 



SECURE PROTOCOL HTTP by its very nature is open to eavesdropping. Packets 
that move across the Internet's routers are full of messages just waiting to be 
sniffed and read. Normally, the fact that you can easily read data sent via HTTP is 
a good thing. It makes the transfer and rendering of information quite easy. 
However, in cases where you need security, HTTP won't work well. 

For example, if you are giving credit-card information to a site— say the commerce 
site you set up— you want to make sure that the information is unreadable. In order to 
do that, you need to make use of the Secure Socket Layer, or SSL. SSL is an additional 
protocol by which the keys and certificates from your site will be transferred to a 
browser or another server. Over SSL, your browser will be able to verify the certificate 
from your site so that it knows you are who you say you are. And sites will be able to 
verify each other. 




Chapter 14: Shopping Cart 



371 




All the encryption in the world will not stop someone who has hacked into 
your box or has legitimate access. Most credit-card theft is done by dishonest 
employees with too much access. 



This has been a quick and dirty introduction to Web security. If you would 
like to learn more, I suggest starting with this page, and following any inter- 
esting links provided there: http:/ /www. mods si .org/docs/2.6/ssl_ 
overview. html 




Encryption and security tools 



Given what you have just read about encryption and security, it probably stands to 
reason that you are going to need some new tools. Here's a basic rundown. 

First off, you are going to need to add SSL to Apache. As with everything else 
discussed in this book, adding SSL does not require you to pay for specialized soft- 
ware. All you need to do is install Apache with mod_ssl. You can read more about 
it at http://www.mod_ssl .org. To get SSL to work with Apache in the United 
States, you will need an additional piece of software called rsaref from RSA. 

The installation of these tools with Apache is well documented in INSTALL. SSL 
file, so we won't cover it here. If you are having trouble getting mod_ssl, PHP, and 
MySQL to work for you, we recommend this site, which goes through the installation 
Step by Step: http://www.devshed.com/Server_Side/PHP/SoothinglySeamless/ 
page8. html . 



Make sure you read about the credit-card authorization options in the 
following section before you configure Apache with PHP. 




But before any of this will work for you, you are going to need to get a certifi- 
cate. As I already mentioned, Verisign is the most frequently used certification 
organization. And if you use a Verisign certificate along with the PayfloPro (which 
is a service of Verisign) functions described below, you may be able to get a good 
deal. Thwate, which used to be the second- largest certification organization, is now 
owned by Verisign. Other options in this area can be found at the following URL: 
http://dmoz. o rg/ Compute rs /Secu r i ty/Publ i c_Key_Inf rastructure/PKIX/ 
Tool s_and_Servi ces/Thi rd_Party_Certi f i cate_Authori ties/. 



372 Part IV: Not So Simple Applications 

Configuring for credit- card authorization 

When Apache is configured with SSL your site will be able to talk to browsers 
securely. If the URL starts with https : //, the browser knows to look on Port 443 and 
to look for a certificate. However, there is still the question of how your site will talk 
with the entity that will process credit cards and either accept or reject the transaction. 
PHP has many great features for dealing with other sites. For instance, fopenO is 
URL-aware. But none of the filesystem or URL functions work with SSL, so you will 
need to make use of a specialized function set or a program outside of PHP. 

I wholeheartedly recommend you look at some of the new options available in 
PHP4 before deciding on which payment processing services to use. I'll cover them 
briefly here. 

PAYFLOPRO 

If you decide to use Verisign to process credit cards, you will want to make use of 
these functions. You will need to install a code library that you will get from Verisign. 
You will then need to compile PHP so that it will recognize the new functions. Details 
can be found at http: //www. php.net /manual /ref.pfpro.php. 

PayfloPro is very easy to work with. A function or two will process your request 
and you will get back a response. Once that response is compared to a set of known 
codes, you will know whether or not the transaction succeeded. This is very nice 
and will help keep your code very clean. 

CYBERCASH 

Similarly, to make use of Cybercash, you will need to do a custom installation of 
PHP, using the libraries that come with the PHP distribution found at http: //www. 
php.net/manual /ref . cybercash . php. The functions that come with PHP are not 
as clean as those that work with PayfloPro. However, there is a nice library in 
/ext/cybercash of your PHP installation that should make credit-card process- 
ing relatively easy. 

CURL: 

This acronym stands for Client URL Library functions. It is a code library that you 
can use for communicating over the Internet using just about any protocol out 
there. It supports Gopher, Telnet, and (the best for our purposes) HTTPS. 

You can now access this library through PHP functions if you installed PHP 
using with the --with-curl flag. You will first have to download the library from 
http: //curl . haxx. se/. 

We don't think the cURL functions are as clean as the PayflowPro functions. 
However, the are open-source. We'll cover the functions in the Code Breakdown 
section. 

Configuring for session handling 

When I start breaking down the code, you will see the exact functions you need to 
work with sessions. But while you are reading about configuration options, it's best 



Chapter 14: Shopping Cart 373 

to cover the different ways sessions can be implemented in PHP. But first a little on 
what sessions in PHP actually do. 

Say you want to track the activity of your users across a number of pages, as with 
this shopping cart. You need to remember who has put what in a cart. To accomplish 
this, you could pass some rather complex variables via a cookie that held the all of the 
elements and their prices. But this is kind of messy, and it may expose more of the 
workings of your application than you are comfortable exposing. Moreover, the 

cookie specification (http: //www. net sea pe.com/newsref/std/ coo kie_spec.html) 
allows for only 20 cookies per domain and only 4 bytes per cookie. 

A better idea is give each person who visits your site a unique identifier, some 
value that identifies who that person is. Then, as the user adds items to the cart, 
information associated with the unique identifier can be stored on the server. If you 
were to code a function that stored the information by hand, you might create a 
unique string that would be put in a cookie; then, in some directory on the server, 
you could have a file that has the same name as the unique user ID. Within that file 
you could store all the variables associated with the user. For example, you might 
have an array of items that a specific user put in his or her cart. 

In fact, this is almost exactly what sessions do. When you indicate in your code 
(or by settings in your php.ini) that you'd like to start a session, PHP will create a 
unique identifier and an associated file, which is stored on the server (the location is 
set in thephp.ini, and by default is in the/tmp directory). Then as a user moves from 
page to page, all the variable information that the user chooses can be stored in the 
file on the server, and all the script needs to keep track of is the unique identifier. 

There are many configuration options when it comes to sessions, but probably 
the most important decision is where the session id will be propagated, in a URL or 
in a cookie. Most e-commerce sites make use of cookies. However, there is the 
chance that some of your users will not be able to use your site properly if they 
have their browsers set to reject cookies. For this reason, in PHP it is very easy to 
add the session id to the querystring. There are two ways to go about it. 

The code <?= SID ?> will print the session id. To append the session id to a URL, 
you would have to manually add it, like this: 

<a href=mydomai n.com?sid = <? = SID?> 

This can make for some tedious work if you want to put the session id on every 
link in your site. However, if you compile PHP with the flag --enabl e -trans -si d, 
the session id will be automatically appended to every relative link in your pages 
once a session has been started. 



Code Overview 



As you might have guessed by now, there are two function sets used here that are 
relatively unique: the functions that deal with sessions and the functions associated 
with thecURL library. I will cover both sets of functions in some detail in this section. 



374 



Part IV: Not So Simple Applications 



First, though, I need to make another note about the advantages of the object- 
oriented approach. When you read Chapter 10 (you did read Chapter 10, right?) you 
saw some of the principles of object-oriented programming in practice. Specifically, 
you should have noticed how inheritance is used. When a class inherits the proper- 
ties and methods of a parent class, it has access to all of the methods and properties 
of the parent. 

In this application, you are extending the code you used in the catalog, so it 
makes sense that this application would create classes that extend the classes used 
in the catalog. Please be sure you are familiar with the catalog classes in Chapter 10 
before proceeding. 

Session functions 

If you head over to the session page in the PHP manual (http://www.php.net/ 
manual /ref . sessi on .php), you will find 16 different functions. Depending on your 
needs, you may have to use a majority of these, but in many circumstances you could 
getaway with using a single function: session_register( ). We'll explain. 



There are many functions and settings regarding sessions that we don't 
cover here. As always, make sure to check the manual. 




The first thing you will need to do is let PHP know that you wish to start a ses- 
sion. You can do that explicitly by using session_start( ). Then you will need to 
let PHP know what variables you want to store. You can do this with sessi on_ 
register ( "vari abi e_name" ). Take the following page, for example: 




The start sessionO and sessi on_register() functions should be 
at the very top ofyourPHP pages.These functions send cookies,which area 
typeof HTTP header.lf you attemptto send anytype of header aftertext has 
been sent to the browser, you will get an error. 



sessi on_start( ) ; 

sessi on_regi ster( "mystri ng" ) ; 



$mystring = "testing for a string"; 
?> 



Chapter 14: Shopping Cart 375 

When accessed the first time, this page will start a session, and depending on the 
configuration either the script will send a cookie or a session id will be appended to 
relative links. The session_register command tells PHP to search the session file 
for the variable $mystring. If it exists, it will become available as a global, or if 
you wish you can access it through the $HTTP_SESSION_VARS array. After the 
page is processed the most current value for the registered variables is written to 
the session file. So if you have another page that contains the following code: 

<? 

sessi on_start( ) ; 

sessi on_regi ster( "mystri ng" ) ; 

echo $ my string; 
?> 

It will print "testing for a string". 

The fact is that the session_start( ) function isn't really necessary; if you have a 
sessi on_register( ) in your code, PHP is smart enough to start the session for you. 

Note that although in the preceding code the variable is a simple string, you are 
by no means limited to a string. Simple arrays, complex arrays, and objects are all 
viable session variables. 

Here are some other session functions that you may find useful. 

SESSION, DESTROYO 

This function kills a session and all of the variables associated with it. 

SESSIONUNREGISTERO 

This function erases the value of a variable in the session file. 

SESSION_SET_SAVE_HANDLER() 

This interesting function allows you to set your own methods for storing, 
retrieving, and writing your own session handlers. 

void sessi on_set_save_handl er (string open, string close, string 
read, string write, string destroy, string gc) 

For a good deal of the time, the file-based session management in PHP will be 
fine. However, there are a couple of circumstances in which it may not suit you. If 
you happen to be working in a clustered environment, where several machines are 
serving the same site, writing to the local filesystem really won't work. Similarly, 
your SSL-enabled Apache installation may sit on a different box than your 
main server. 



376 Part IV: Not So Simple Applications 

In this case a better choice is to have all of the machines connect to the same 
database, and to have your database (MySQL, of course) store the session data. It 
was unnecessary for us to make use of this function when we created this applica- 
tion because we were only working with one physical server. However, if you need 
to store session data in a MySQL database, you can use the functions in Appendix G. 

SESSION_ENCODE() 

In order to write variables to a database, the variables needs to be put in a format 
that makes sense to the database. That is what the session_encode function does. 
You can see examples of this in Appendix G. 

$str = sessi on_encode( string) 

SESSIONDECODEO This function reverses the process of encoding, so that 
the variable is turned into a representation that PHP can work with. You can see 
examples of this in Appendix G. 

cURL functions 

You can usethecURL library for many things, but for the shopping cart application, 
you are only concerned with one piece of functionality: communicating with a 
credit-card validation service. Basically, the application will send a secure message 
over HTTPS, and the service that validates credit cards will send back a response, 
which can then be processed in PHP. 

There are only four cURL functions to work with. 

CURLJNITO 

This function returns an integer that is similar to the result identifier returned by 
mysqi_connect( ) or the file pointer returned by fopen( ). In this case, it's called 
the cURL handle, or ch. In the sole argument of this function you indicate the URL 
you wish to access. 

int curl _i nit ([string url]) 

For example: 

$cc_company_url = 

"https: //secure. process. site/transact.dll?exp=foo&cardtype=bar 

$ch = curl_i ni t( $cc_company_url ) ; 

Note that this function only starts the cURL session. The call to the URL doesn't 
happen until the curi_exec( ) function is executed. 



Chapter 14: Shopping Cart 377 

CURL_SETOPT() 

bool curl_setopt (int ch, string option, mixed value) 

Before you execute the communication, there are over 40 options you can set for 
cURL. Many of these aren't really necessary, given the quality of PHP's function set. 
Others aren't really relevant to the application presented here. See the manual 

(http://www.php.net/manual /ref .curl .php) if you'd like to see the full list of 

cURL's functions. For the sake of this application, all you need is to have the results of 
the https request returned to a PHP variable. For that you can use the CURLOPT_ 
RETURNTRANSFER option. 

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 

CURL_EXEC() 

This function executes the transfer. The one argument should be the results of the 
curi_init( ) function and you should set all the necessary options. 

bool curl_exec (int ch) 

CURL_CLOSE() 

This function finishes the cURL connection using the curl handle: 

void curl _c lose (int ch) 

In the end, this set of functions will conduct the transaction and return a result 
to the $data variable. 

$ch = curl_i ni t($authori ze_net_url ) ; 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
$data = curl_exec( $ch ) ; 
curl_cl ose( $ch ) ; 

Dealing with the credit- card processor 

You are going to need to get some information directly from the entity processing the 
transaction. Most processing companies that we've seen work similarly. You send 
them a request with all the expected credit card information: number, expiration date, 
address, and so forth, and they send you some codes in response. 

Your PHP script will need to compare the codes it receives with the values you 
get from the processing agency. 



378 



Part IV: Not So Simple Applications 



For this application you will use Authorize.net as the credit card processor, 
which seems to work just fine. 




Remember to look into the Payfl oPro and Cybercash functions before 
settling on a payment method. 



Code Breakdown 



As with the Catalog, here you'll start by looking at the classes that will come into 
play in this application. Again, the files accessed via the URLs are very high-level 
files; all the tough work is done in the Class files. 

A peek at classes.php will show the classes of interest here: 



cart_base_class.php"; 
. . /catal og/category_cl ass . php 
. ./catalog/product_class.php" 
. ./catalog/style_class.php"; 
. . /catal og/substyl e_cl ass . php 

cart_category_cl ass . php" ; 
cart_product_cl ass .php" ; 
cart_styl e_cl ass . php" ; 
cart_substyl e_cl ass . php" ; 

user_cl ass .php" ; 
address_cl ass . php" ; 
order_cl ass . php" ; 
i tem_cl ass .php" ; 



As already mentioned, one of the goals of this application is to make use of the 
classes we created in the catalog. We want to write as little new code as possible. So 
the new classes here will inherit the methods and properties in the classes we 
already created. 

One class from Chapter 10 doesn't quite do enough for inclusion in the cart. That 
is the Base class. We're going to create another Base class with some extended 
functionality. Then all we have to do is make sure that the categories that extend 
Base call our new version. This is easily done with includes. In our classes.php file 
we include the new Base class, and then, when a class that extends Base is 
included, it sees the new class. When you look at the classes.php file, remember that 
the entire content of each of the included files is sucked into this file when it is 
parsed by PHP. 



i nc~ 


ude " 


i nc~ 


ude " 


i nc~ 


ude " 


i nc~ 


ude " 


i nc~ 


ude " 


i nc~ 


ude " 


i nc~ 


ude " 


i nc~ 


ude " 


i nc~ 


ude " 


i nc~ 


ude " 


i nc~ 


ude " 


i nc~ 


ude " 


i nc~ 


ude " 



Chapter 14: Shopping Cart 379 

It will be easier to get a feel for the inheritance chain with some visual represen- 
tation. Figure 14-6 shows the inheritance chain. 



Category 



Product 



Style 



Substyle 



Base 



cart_category 



User 



cart_ product 



cart_ style 



Q 



Order 










v 




Item 



cart_ style 



Figure 14-6: Cart Classes Inheritance Chain 



380 



Part IV: Not So Simple Applications 



As you can see from this figure, inheritance can be a bit more complicated than 
a straight hierarchy. Keep this figure handy as you read through this chapter and 
the source files on the CD-ROM . 

Classes 

The first classes we will discuss are on the right-hand side of Figure 14-6. 

These classes have methods that look very much I ike the methods in the Category, 
Product, and other classes from Chapter 10. Those worked well because there is a 
natural hierarchy when dealing with products: categories contain products, products 
contain styles, and styles contain sub-styles. For a shopping cart there is a hierarchy 
of user information: a user can have many addresses, many orders can go to an 
address, and many items can belong in a single order. 

Given this similarity it makes sense to create classes for user information that 
mimic the classes seen in Chapter 10. You will see what I mean when I get to the 
User class 

Let's start at the top of the chain, looking at the changes to the Base class. 

BASE CLASS 

PHP classes do not support multiple inheritance. If they did, we could create a class 
named CartBase as an extension of Base, and then have the Catalog class extend 
both Base and CartBases. Since this isn't possible, we copied all of the methods 

from the Base class in the catalog (sql_format( ), function set_image_src ( ), 

set_thumb_src( ), construct! ), and base( )). All of these methods are described 
in Chapter 10. We added a single method, which prints a form. 

METHOD ORDER_FORM() There's nothing terribly special in this form. It will print 
all the data necessary to gather information on an item to be put in the shopping cart. 

function order_form () 



$output = start_f orm( "cart . php" ) 

"<b>".$this->item."</b>" 

"&nbsp ; " 

tthis->price 

"&nbsp ; " 

"<b>Qty</b> " 
. text_f i el d( " quant i ty" , 1 ,4 ,4) 

"&nbsp ; " 

, submi t_f i el cK "order_i tern" , "Order !" ) 
, hi dden_f i el cK "category_id" , $thi s->category_id: 
, hi dden_f i el cK "product_i d" , $thi s->product_i d) 
, hi dden_f i el d( "styl e_id" , $thi s->styl e_i d) 
, hi dden_f i el d( "substyl e_id" ,$this->substyl e_id: 
, h i d d e n_f i e 1 d ( " p r i c e " , $ t h i s - > p r i c e ) 



Chapter 14: Shopping Cart 381 



. end_form( ) 

$output . = "\n": 
return loutput; 



USER CLASS 

As mentioned, this class will look very much like the classes from the catalog. 
Here's the vital information on this class: 

Class Name: User 

Extends Base 

Default Properties: 

♦ $ addresses 
Methods: 

♦ User. The class constructor. Takes two arguments: $parent and $atts. 
Calls the Based method, assigning each element of the $atts array to 
Object properties. 

♦ FetchUser. Takes one argument, $user_id. Creates object properties for 
every row in the user table associated with the $user_id. 

♦ LoadUser. Takes one argument, $user_id. First runs FetchUser and then 
creates an array, each element of which is an object containing address 
information. 

♦ SaveUser. Takes no arguments, assumes a $this->user_id exists. Will 
both update existing styles and create new ones as needed. 

♦ DeleteUser. Takes no arguments. Removes a user from the database. It 
will force confirmation if there are related addresses. It will delete the 
user after confirmation is provided. 

This should look familiar. You should expect to see the same sorts of data structures 
that were created in Chapter 10 and shown in Figures 10-11 and 10-12. Using this 
class you can expect an object created from the User class to contain an array called 
address, each element of which is an object containing complete address information. 

CLASS ADDRESS 

This looks very similar to the previous class. 
Class Name: Address 
Extends User 
Default Properties: 

♦ $orders 



382 Part IV: Not So Simple Applications 



Methods: 



Address. Calls the Based method, assigning each element of the $atts 
array to Object properties. 

FetchAddress. Takes one argument, $address_id. Creates object properties 
for every row in the user table associated with the $address_id. 

LoadAddress. Takes one argument, $address_id. First runs FetchOrder and 
then creates an array, each element of which is an object containing 
address information. 

SaveAddress. Takes no arguments, assumes a $this->order_id exists. Will 
both update existing styles and create new ones as needed. 

DeleteAddress. Takes no arguments. Removes a user from the database. It 
will force confirmation if there are related addresses. It will delete the user 
after confirmation is provided. 



CLASS ORDER 

Finally, in this class we add something new and interesting. 
Class Name: Order 
Extends Address 
Default Properties: 

♦ $items 
Methods: 

♦ Order. Calls the Based method, assigning each element of the $atts array 
to Object properties. 

♦ FetchOrder. Takes one argument, $order_id. Creates object properties for 
every row in the user table associated with the $order_id. 

♦ LoadOrder. Takes one argument, $order_id. First runs FetchOrder and then 
creates an array, each element of which is an object containing address 
information. 

♦ SaveOrder. Takes no arguments, assumes a $this->order_id exists. Will 
both update existing styles and create new ones as needed. 

♦ DeleteOrder. Takes no arguments. Removes a user from the database. It 
will force confirmation if there are related addresses. It will delete the user 
after confirmation is provided. 

♦ CalculateTotals. Takes no arguments. This method calculates total prices 
for each item of the order and for the order as a whole. Items that are 
flagged for deletion are removed from the items array property. 



Chapter 14: Shopping Cart 383 

♦ ValidateCard. Takes no arguments. This method does not conduct a trans- 
action with a credit-card processing agency. It runs through a function 
(seen in Appendix G) that makes sure the credit card number supplied is 
potentially valid. If the credit card is not in a proper format, the method 
will return false. 

♦ ChargeCard. Takes no arguments. Communicates with the credit-card 
processor. 

♦ PrintOrder. Takes no arguments. Prints out the results of an order. 

The ChargeCard method deserves some added discussion. 

METHOD CHARGECARDO This method will make use of the cURL functions 
described earlier in this chapter. First we start by calculating the totals. 

function ChargeCarcK) 
t 

$this->CalculateTotals( ) ; 

$total_charged = $thi s->total_pri ce + $thi s - > s h i p_cost ; 

$exp = sprintf("%02d/%04d", 

$thi s->cc_exp_mon , $thi s->cc_exp_yr) ; 

The following code prepares a URL that we will use to communicate with autho- 
rize.net. For legal reasons we did not include the actual variables you will need to send 
to authorize.net to get a meaningful response. However, that information is available 
at the authorize.net site. Following that, we prepare an error message, just in case. 

$authori ze_net_url = 

"https://url .to. author ize.net?varl= FA LSE&var2=foo" ; 

$this->error = "connection to authorize.net failed"; 

Now it is time to make the connection and see if the credit card is verified. The 
cURL functions are a little strange at this point. They are fairly new and aren't quite 
as polished as some of the other function sets. But by the time you read this, there 
may be a PEAR (PHP Extension and Application Repository) class that makes dealing 
with cURL easier. Make sure to check in at the php.net site for the latest updates to the 
cURL functions. 



global $ch; 

$ch = curl_i ni t($authori ze_net_url ) ; 

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1): 

$data = curl_exec( $ch ) ; 

curl_cl ose( $ch ) ; 



384 Part IV: Not So Simple Applications 

authorize.net returns a string of comma-separated values. The script will compare 
the results from the returned string against their known meanings (supplied by 
authorize.net). In the code that follows, the first element in the returned string is 
tested. If it has a value of 1, this method will return TRUE, meaning the transaction 
was successful. 

$thi s->rvars = expl ode( " , " , $buffer) ; 
$thi s->auth_resul t = $thi s->rvars[0] ; 
$this->error = $this->rvars[3]; 
if ($thi s->auth_resul t != 1) ( return FALSE; ) 
return TRUE; 
} 

CLASS ITEM 

This class is at the base of the hierarchy. 
Default Properties: 

♦ none 
Methods: 

♦ Item. The class constructor. Takes two arguments: $parent and $atts. Calls 
the Base() method, assigning each element of the $atts array to Object 
properties. 

♦ Saveltem. Takes no arguments, assumes a $this->item_id exists. Will both 
update existing styles and create new ones as needed. 

CLASS CARTCATEGORY 

This is the first of the new classes that directly extend the classes created for the 
catalog application. 

Class Name: CartCategory 

Extends Category 

Default Properties: 

♦ none 
Methods: 

♦ CartCategory. The class constructor. Calls the Based method, assigning 
each element of the $atts array to Object properties. 

♦ AddProduct. This method overwrites the AddProductd method that is in 
the parent (Category) class. It creates an array, called $products, each 
member of which is an object formed by calling the CartProductsO class. 



Chapter 14: Shopping Cart 385 

In creating the data structure in Chapter 10, you saw how the LoadCategoryQ 
method instantiated objects within an array. Instead of having the LoadCategoryQ 
class instantiate the object directly, we had LoadCategoryQ call another method, 
named AddProductQ, which instantiated the objects. By breaking out AddProductQ 
into its own separate method, we gained some flexibility, which becomes convenient 
in this application. 

When you instantiate the CartCategory class, the AddProductQ method of this 
child class overwrites the AddProductQ method of the parent (Category) class. So if 
you write the following code: 

$c = new CartCategory; 
$c->l_oadProduct( $product_i d) ; 

you can be sure that the AddProductQ method from the CartCategory call 
will execute. 

Here are the contents of AddProductQ method of the CartCategory class. 

function AddProduct($parent,$atts) 
{ 

$thi s->products[] = new CartProduct($parent , $atts ) ; 



CLASS CARTPRODUCT 

This is similar to the CartCategory class and includes a method for printing 
Products that is better for the shopping cart. 

Class Name: CartProduct 

Extends Product 

Default Properties: 

♦ none 
Methods: 

♦ CartProduct. The class constructor. Takes two arguments: $parent and $atts. 
Calls the Based method, assigning each element of the $atts array to Object 
properties. 

♦ AddStyle. This method overwrites the AddStyleQ method that is in the 
parent (Product) class. It creates an array, called $styles, each member of 
which is an object formed by calling the CartStylesQ class. 

♦ PrintProdct. Takes no attributes. Overwrites the PrintProductQ method of 
the parent (Product) class. 



386 Part IV: Not So Simple Applications 

CLASS CARTSTYLE 

This is similar to the CartStyle class and includes a method for printing Styles that 
is better for the shopping cart. 

Class Name: CartStyle 

Extends Style 

Default Properties: 

♦ none 
Methods: 

♦ CartStyle. The class constructor. Takes two arguments: $parent and $atts. 
Calls the BaseQ method, assigning each element of the $atts array to 
Object properties. 

♦ AddSubStyle. This method overwrites the AddSubStyleQ method that is in 
the parent (Style) class. It creates an array, called $substyles, each member 
of which is an object formed by calling the CartSubStylesQ class. 

♦ PrintStyleRow. Takes no attributes. Overwrites the PrintStyleRowQ method 
of the parent (Style) class. 

CLASS CARTSUBSTYLE 

This is similar to the CartSubStyle class and includes a method for printing substyles 
that is better for the shopping cart. 

Class Name: CartSubStyle 

Extends SubStyle 

Default Properties: 

♦ none 
Methods: 

♦ CartSubStyle. The class constructor. Takes two arguments: $parent and $atts. 
Calls the BaseQ method, assigning each element of the $atts array to Object 
properties. 

♦ AddSubStyle. This method overwrites the AddSubStyleQ method that is in 
the parent (Style) class. It creates an array, called $substyles, each member 
of which is an object formed by calling the CartSubStylesQ class. 

♦ PrintSubStyle. Takes three attributes, $style_price, $style_dsc, $product_dsc. 
Overwrites the PrintSubStyleQ method of the parent (Style) class. 



Chapter 14: Shopping Cart 387 



Scripts 



These are the pages called by URLs and the includes. Once again, you will probably 
notice that there isn't a whole lot involved. Almost all of the work is done in 
the Classes. 

DISPLAY.PHP 

This will print out either a list of categories or a specific product. 

include 'header. php ' ; 

if (empty ( $category_id) ) 
( 

header("Location: index.php"); 

exit; 
} 

$page_title = anchor_tag( "i ndex. php" , "Bag ' ' Stuff ") ; 

$c = new CartCategory ; 

if (empty ( $pnoduct_id) ) 
t 

$c->LoadCategory( $category_id) ; 

$page_title .= ": $c->categony " ; 
include "stant_page . php" ; 

$c->PrintCategory( ) ; 
} 

el se 
t 

$p = new CartProduct; 
$p->LoadProduct($product_i d) ; 
$p->LoadStyles() ; 

$c->FetchCategory( $p->category_id) ; 
$page_title . = ": " 

. anchor_tag( "di spl ay . php?category_i d = $c->categony_i d" 

, $c->category 
) 
. " : $p->product" 



388 Part IV: Not So Simple Applications 

include "start_page .php" ; 

$ p - > P r i ntProduct( ) ; 
} 

include "end_page. php" ; 

It doesn't get a whole lot more basic: If this page is to display a Category (not a 
product), a Category is loaded and then printed. The same will happen for a Product if 
appropriate. If you remember the display.php page from Chapter 10, you might notice 
that the only real difference is that the objects instantiated here are created from the 
classes new to this application. That gives us access to the new print methods, which 
were designed to work with this application. 

CART.PHP 

Here's the page that creates our shopping cart. 

include "header. php"; 

sessi on_regi ster( "cart" ) ; 
sessi on_regi ster( "1 ast_i tern" ) ; 

$page_title = anchor_tag( "i ndex. php" , "Bag ' ' Stuff ")." : 
Shoppi ng Cart" ; 

include "start_page .php" ; 
include "cart_form. php" ; 
include "end_page. php" ; 

What? Expecting a little more code from your shopping cart? Well, most of it is 
in the include (cart_ form. php). J ust note here that the session is started. And that 
there are two session variables you will be tracking. The cart object, as you will see 
in a moment, is created with the Order class. Remember that when the two variables 
are registered on the page, two things happen. First, if they exist already, they are 
pulled into memory from the session file. Second, if they are changed in the course 
of the page, they will be written out with those changes at the end of the page. 

Note that the $last_item variable holds the description of the last item ordered 
by the user. We use it to prevent multiple orders of the same item, typically caused 
by the user hitting the Order! button more than once. If the user wants two of an 
item instead of one, they can change the quantity for the item. 

Now let's look at the include. 

CART_ FORM. PHP 

As you can see here, if the cart does not exist in the session a new one will be instan- 
tiated. There are extensive in-line comments, which should help you get through 
this script. 



Chapter 14: Shopping Cart 389 



// if $cart is not an Order object, make it one 
if ( ! i s_object( $cart) ) { $cart = new Order; ) 



// initialize the URL for displaying 

// items from the cart with the name 

// of the display script, formatted as 

// an absolute URL to the regular 

// (non-secure) server by the regul ar_url ( ) function (defined in 

// /book/f uncti ons/basi c. php) . 

$href = regul ar_url ( "di spl ay . php" ) ; 

if ($order_item == "Order!") 
t 

// create a new CartSubStyle object, as the lowest class in our 

// class hierarchy (low == furthest from Base), 

// it is an extension 

// of all the other classes, and so can access 

// all of their methods. 

// we will use this to hold any new item to be 

// added to the cart. 

$t = new CartSubStyle; 

// the $last_item variable holds the description of the last 

// item ordered by the user, we use it to prevent multiple 

// orders of the same item, typically caused by an itchy 

// finger on the 'Order!' button, if the user wants two of 

// an item instead of one, they can change the quantity for 

// the item. 

// if the cart is empty, of course, there is no previously 
// ordered item - set $last_item to an empty string, 
if (count( $cart->i terns ) == 0) ( $last_item = ""; } 

// the $item variable will be set to a description of the 
// ordered item, initialize it to an empty string. 
$i tern = " " ; 

if (! empty ( $product_id) ) 

{ 

// we at least have a product ID. get information about 
// the product category and its category from the database, 
// and add links to the category and product to the item 
// description. 
$t->FetchCategory( $category_id) ; 



390 



Part IV: Not So Simple Applications 



$href .= "?category_id=$t->category_id" ; 
litem .= anchor_tag( Ihref , $t->category ) ; 

$t->FetchProduct($product_id) ; 

$href .= "&product_i d=$t->product_i d" ; 

litem .= "- " . anchor_tag( $href , $t->product ) ; 
} 

if ( ! empty ( $styl e_i d) ) 
{ 

// we have a style ID. get information about the style 

// from the database and add the style name to the item 

// description, (styles are not individually displayed.) 

$t->FetchStyle($style_id) ; 

litem .= "- $t->style"; 
1 

if ( lempty ( Ssubstyl e_id) ) 
{ 

// we have a substyle ID. get information about the substyle 

// from the database and add the substyle name to the item 

// description, (substyles are not individually displayed.) 

$t->FetchSubStyle($substyle_id) ; 

litem .= "- $t->substyl e" ; 



i f ( ! empty ( litem) 



$last_item != litem) 



// if we have an item description and it is not the 
// same as the last item ordered, add the new item 
// to the user's shopping cart. 
$cart->AddItem( $cart , array( 
"i tem"=>$i tern 

"product_id" => $product_id 

"style_id" => $style_id 

"substyl e_id" => $substyle_id 

"price" => $price 

"quantity" => Iquantity 
)); 



// set $last_item to the item just ordered (if any) 
$last_item = $item; 



else if ($ again == "please") 
{ 

// which just means, we're coming from a submitted cart form, 



Chapter 14: Shopping Cart 391 



// where $again is set to "please" in a hidden field, we test 

// this, rather than the value of $submit as in other examples, 

// so the user can hit the ENTER key after typing in a new 

// quantity or checking remove boxes and get a recalculation, 

// without actually pressing the 'Recalculate' button. 

// for each item in the cart, set its quantity property 
// to the corresponding value from the $quantity[] array 
// built by PHP from the ' quantity [$row] ' fields submitted 
// from the form. 
$quantity = (array )$quanti ty ; 

reset( $cart->i terns ) ; 

while (list($row,) = each( $cart->i terns ) ) 

{ 

// by adding explicitly, PHP will set the value 
// of the quantity property to at least 0, even if, 
// for some reason, the user has set the field to 
// a blank val ue. 
$cart->i tems[$row] ->quanti ty = $quanti ty[$row] + 0; 



Sremove = ( array )$remove; 

while (1 i st( $row, $val ue) = each( Sremove) ) 

{ 

// tag the item for removal by Cal cul ateTotal s( ) 
$cart->i tems[$row] ->ki 1 1 me = lvalue; 



// recalculate the total price for each item in the cart 
$cart->Cal cul ateTotal s( ) ; 

// display the contents of the shopping cart 

print start_form( ) ; 

print hidden_f i el d( "agai n" , "pi ease" ) ; 

print start_tabl e(array( " border "=>1 ) ) ; 

print tabl e_row( "<b>Item</b>" 

, "<b>Quantity</b>" 

, "<b>Price</b>" 

, "<b>Total</b>" 

, "<b>Remove?</b>" 
); 



392 Part IV: Not So Simple Applications 



reset( $cart->i terns ) ; 

while (1 ist($row, $i tern) = each( $cart->i terns ) ) 

{ 

// display each item in the cart, the item description 
// will include links to the display page for the 
// various elements of the item (category, product, 
// style, as applicable), (see above where litem is 
// constructed . ) 

// display the total price for each item as a US dollar 
// value (2 decimal places with a dollar sign in front). 

// display a checkbox allowing the user to remove an item 

// from the cart. 

print tabl e_row( $i tem->i tern 

, text_f iel d( "quanti ty [$row] " , $i tem->quanti ty , 3) 
, tabl e_cel 1 ( $i tem->pri ce , array ( "al i gn"=>" ri ght" ) ) 
, tabl e_cel 1 (money ($item->total_price) 
, array ( "al i gn"=>" ri ght" ) 



checkbox_f iel d( " remove[$row] " , "yes", "remove") 



); 



// keep a running total of the quantity and price of items 
// in the cart. 

$total_price += $i tem->quanti ty * $i tem->pri ce ; 
$total_quanti ty += $i tem->quanti ty ; 



// print out totals 

print tabl e_row( "<b>Grand Total :</b>" 
, "<b>$cart->total_quantity</b>" 

, tabl e_cel 1 ( "<b>" .money ( $cart->total_pri ce) . "</b>" 

, array ( "al i gn"=>" ri ght" ) 
) 

); 

print end_tabl e( ) ; 

// the 'Continue Shopping' button displayed by the keep_shoppi ng( ) 
// function (defined in f uncti ons . php) runs in its own form. 
// so we display it in an HTML table with the 'Recalculate' 
// button of the shopping cart form to keep them side-by-side, 
print start_tabl e( ) ; 



Chapter 14: Shopping Cart 393 

print table_row( 

submi t_f i el d( " recal c" , "Recal cul ate" ) 
. end_form( ) 

//keep shopping is defined in the f uncti ons . php file 

keep_shoppi ng( $category_id , $product_id) 
); 
print end_tabl e( ) ; 

CHECKOUT.PHP 

Now, finally, it's time to check out. Note that this is really the only file that needs to be 
on the secure server. There's no need for the catalog portions or even the cart page to 
be on a secure server, because there's really no information that needs to be protected. 
However, on this page we're going to be accepting credit card information. 

Once again, there are extensive comments within the script to help you get 
through the page's logic. 

include "header. php"; 

// if a session ID has been passed in, use it 
if ( i sset( Isessid) ) ( sessi on_i d( $sessi d) ; 1 

// get the session variables for the shopping cart and the user's 

email address 

sessi on_regi ster( "cart" ) ; 

sessi on_regi ster( "emai 1 " ) ; 

// if a value for 'email' was posted to the script from a form, use 

that 

// in preference to the session variable 

if (!empty($HTTP_POST_VARS["email"])) ( $email = 

$HTTP_POST_VARS[" email"]; } 

// set up variables defining the values of the buttons of the form 

// (defining the values once helps avoid errors caused by spieling 

probl ems . ) 

$order_button = "ORDER NOW!"; 

$info_button = "Get My Info"; 

if ( ! i s_object( $cart) ) 
t 

// if $cart isn't an Order class object (defined in 
order_cl ass . php) , 

// we're not going to do much - the shopping cart will have no 



394 Part IV: Not So Simple Applications 

// items in it. initialize it as one anyway, to keep the script 
from 

// breaking. 
$cart = new Order; 



// load any posted variables into the cart using the ConstructO 
// method of the Base class (defined in cart_base_cl ass .php) . 
$cart->Construct($HTTP_POST_VARS) ; 

if ( lempty ( $zapthi s ) && i s_array ( $zapthi s ) ) 
{ 

// if multiple addresses were found for the user from past 
orders , 

// the user can ask to have one or more of the addresses removed 

// from the database, this is done by checking HTML checkbox 
fields 

// named "zapthi s[] " , set to the ID values of the addresses. 

// if at least one of the checkboxes was set, PHP will return 

// the values in array variable $zapthis. (if no checkboxes 

// are checked, Izapthis is not necessarily empty, but it 

// will not be an array . ) 

while ( 1 i s t ( , $ a i d ) = each($zapthis)) 

{ 

// delete the address records using the Del eteAddress ( ) 
// method of the Address class (defined in 
address_cl ass . php) 

$cart->Del eteAddress ($ai d) ; 



i f ( lempty ( $usethi s ) ) 
{ 

// if multiple addresses were found for the user from past 
orders , 

// the user can ask to use one of them for this order by 
clicking 

// on a radio button field named "usethis", set to the ID value 

// of the address, if $usethis is set, get the address record 

// for the ID value, using the FetchAddress( ) method of the 

// Address class. 

if ($cart->FetchAddress($usethis)) 

{ 

// there is now one and only one address for this order 
$cart->address_count = 1; 



Chapter 14: Shopping Cart 395 



if ($ordernow == $order_button '. 



II the user hit the big ORDER button, validate their credit 

// card and charge it, using the Val idateCard( ) and ChargeCardL 

// methods of the Order class. 

if ($cart->ValidateCard() && $cart->ChargeCard( ) ) 

{ 

// the charge went through - write the order to the 

// database using the SaveOrderO method of the Order class. 

$cart->SaveOrder( ) ; 

// redirect the user to the receipt page for a receipt 
// they can print or save to a file, and exit the script. 
// pass on the ID value of the new order record and 
// the session ID that was passed in to this script. 
header("Location: receipt. php?order_id=$cart->order_id" 

. "&sessid=$sessid" 
); 
exit; 



elseif ($getdata == $info_button || (empty ( $cart->user_id) && 

! empty ($emai 1 ) ) ) 

t 

// either the user has asked to look up their information in the 
// database, or we don't yet have an ID value for the user but 
// do have an email address, use the LoadllserO method of the 
// User class (defined in user_cl ass . php) to try looking up 
// address information stored for the user from past orders. 
$cart->LoadUser($email ) ; 



$ p a g e_t i 1 1 e = "Check Out"; 
include "start_page . php" ; 

// include the shopping cart form 
include "cart_form.php" ; 

// begin the order form, we pass on the session ID value that was 

passed 

// into this script as a GET-style argument because it makes it 

easier 

// to see if we're in the right session or not. 

print start_form( "checkout. php?sessid=$sess id"); 



396 Part IV: Not So Simple Applications 

// store the user ID of the user (if any) 
print hi dden_f i el d ( "user_id" , $cart->user_id) ; 

print subti tl e( "User Info"); 

print start_tabl e( ) ; 

// display the user's email address, along with the button they 
// can use to ask to check the database for address information, 
print tabl e_row( "<b>Emai 1 : </b>" , text_f i el d( "emai 1 " , $cart->emai 1 , 20) 

. submi t_f i el d( "getdata" , $i nfo_button ) 
); 

print tabl e_row( "<b>Fi rst Name: </b>" , text_f i eld("firstname",$cart- 
>firstname,40,40)) ; 

print tabl e_row( "<b>Last Name:</b>" , text_f i eld("lastname",$cart- 
>lastname,40,40)) ; 

print tabl e_row( " " ) ; 

if ( $cart->address_count == 1) 
{ 

// if we've only got one address, load its properties 

// as properties of the shopping cart, the easy case. 

$cart->Construct(get_object_vars(&$cart->addresses[0] ) ) ; 
) 

else if ($ cart- >add res s_count > 1) 
{ 

// we have more than one possible address from the database 

// for the user, the hard case. 

// begin building an HTML table to display them. 

$useme_cell = start_tabl e(array ( "border"=>l ) ) ; 

// begin building a list of address cells 
$useme_row = " " ; 

// walk through the array of addresses 
while ( 1 i s t ( $ i , ) = each($cart->addresses)) 
{ 

// use a reference to avoid copying the object 

$a = &$cart->addresses[$i ] ; 

// build an HTML table cell containing the address 
// and fields for the user to indicate how they 



Chapter 14: Shopping Cart 397 



// would like to use it (if at all), and add the 
// cell to the row string. 
$useme_row .= table_cell( 
$a->Pri ntAddress ( ) 
."<br>" 

. r a d i o_f i e 1 d ( " u s e t h i s " 
, $a->address_id 
, "Use this address" 
) 

."<br>" 

. checkbox_f iel d( "zapthi s[] " 
, $a->address_i d 
, "Delete this address" 
) 



// add the address cells and close the table. 

// (note: this somewhat presumes there 

// won't be more than two or three addresses - the table 

// will get unwieldy otherwise.) 

$useme_cell .= tabl e_row( $useme_row) ; 

$useme_cell .= end_table(); 

// display the addresses 

print tabl e_row( " " , tabl e_cel 1 ( $useme_cel 1 ) ) ; 

print tabl e_row( " " ) ; 



// these fields contain any address information that might have been 

// directly entered by the user before the database was searched, or 

// the information from an address from the database that has been 

// selected by the user, in any case, *these* fields are what will 

// be used in the order. 

print tabl e_row( " <b> Ad dress :</b>" , text_f i eld ("address" , $cart- 

>address,40,40)) ; 

print tabl e_row( " " , text_f i eld("address2",$cart->address2,40,40)); 

print tabl e_row( "<b>Ci ty : </b>" , text_f i el d( "ci ty " , $cart- 

>city,40,40)); 

print table_row("<b>State:</b>",sel ect_f i el d( " state" , states ( ) ,$cart- 

>state) ) ; 

print tabl e_row( "<b>Zi p: </b>" , text_f i eld("zip",$cart->zip,10,10)); 

print table_row("<b>Phone:</b>" , text_f i el d( " phone " , $cart- 

>phone,20,20)) ; 



398 Part IV: Not So Simple Applications 

if ( lempty ( $cart->address_i d) ) 
{ 

// allow the user to create a new address 
print table_row("" 

, checkbox_f iel d( "save_as_new" 
, "yes" 

, "Save this as a new address" 
) 



print end_tabl e( ) ; 

// display the available shipping methods 
print subtitleC "Shipping Info"); 

print start_tabl e( ) ; 

print table_row( 

"<b>Shipping Method</b>" 

, tabl e_cel l("<b>Per Order</b>" , array ( "al i gn" = >"ri ght" ) ) 

, tabl e_cel 1 ( "<b>Per Item</b>" .array ( "al i gn"=>" ri ght" ) ) 

, tabl e_cel 1 ( "<b>Total for This 
Order</b>" .array ( "al i gn" = >" ri ght" ) ) 
); 

// if no shipping method has been chosen, use the first one as a 

defaul t 

if (empty ( $cart->shi ppi ng_i d) ) ( $cart->shi ppi ng_id = 1; ) 

// get the list of shipping methods from the database 

Iresult = safe_query ( "sel ect shi ppi ng_i d , shi ppi ng , per_i tern, per_order 

from shipping 
"); 

while ($ship = mysql_fetch_object( $resul t) ) 
{ 

// calculate the cost of using this method, we use a simplistic 

// system: a fixed cost per order, and a per item charge. 

Ishiptotal = $shi p->per_order + ( $cart->total_quanti ty * $ s hi p - 
>per_i tern) ; 

// display the shipping method with a radio field allowing the 
// user to choose it 



Chapter 14: Shopping Cart 399 

print table_row( 

r a d i o_f iel d( "shippin g_i d " , $ s h i p - > s h i p p i n g_i d , $ s h i p - 
>shipping,$cart->shipping_id) 

, tabl e_cel 1 (money ( $ s h i p- 
>per_order ) , array ( "al i gn"=>"ri ght" ) ) 

, tabl e_cel 1 (money ( $ s h i p->per_i tern) , array ( "al i gn"=>"ri ght" ) ) 

, tabl e_cel 1 (money ( $ s h i ptotal ) , array ( "al i gn" = >"ri ght" ) ) 



print end_tabl e( ) ; 

// display payment information 
print subti tl e( "Credi t Card Info"); 

print start_tabl e( ) ; 

if ($cart->error) 
{ 

// if the user tried to place an order and there was an error 
// when validating or charging the card, display it here, 
print table_row( 

tabl e_cel 1 ( "<f ont color=red>$cart->error</font>" 

,array( "col span " = >2) 
) 
); 
$cart->error = ""; 



// display a test card number in the form for this example by 

def aul t . 

// it has a valid format, and since we're not really trying 

// to charge any cards here, AuthorizeNet will accept it. 

if (empty ( $cart->cc_number ) ) { $cart->cc_number = "4912-7398-07156"; 



// pick Visa as the default type, to match the default test card 

number 

if (empty ( $cart->cc_type_code) ) ( $cart->cc_type_code = "vis"; ) 

// use the db_radi o_f i el d( ) function (defined in 

/book/functions/forms.php) 

// to display the accepted credit card types as radio button fields 



400 Part IV: Not So Simple Applications 

print tabl e_row( "<b>Credi t Card:</b>" 

db_radi o_f i el d( "cc_type_code" , "cc_types" , "cc_type_code" , "cc_type" 
, "cc_type_code" , $cart->cc_type_code 
) 
); 

print tabl e_row( "<b>N umber :</b>" , text_f i el d( "cc_n umber" , $cart- 
>cc_number ,20) ) ; 

// set the variables used to enter the credit card expiration date 

// set the $months array to a list of possible months 
for ($i = 1; $i <= 12; $i++) ( $months[$i] = $i ; ) 

// set the $years array to a list of plausible years 
for ($i = 2000; $i <= 2005; $i++) ( $years[$i] = $i ; ) 

// use January 2001 as a default expiration date 

if (empty ( $cart->cc_exp_mon ) ) { $cart->cc_exp_mon = 1; } 

if (empty ( $cart->cc_exp_yr ) ) ( $cart->cc_exp_yr = 2001; ) 

print tabl e_row( "<b>Expires:</b>" 

, sel ect_f i el d( "cc_exp_mon" , Imonths , $cart->cc_exp_mon) 
. sel ect_f i el d( "cc_exp_yr" , $years , $cart->cc_exp_yr) 
); 
print end_tabl e( ) ; 

// save the ID of the address used in this order (if any) 
print hi dden_f i el d( "address_id" , $cart->address_i d) ; 

// display the order button 

print paragraph ( submi t_f iel d( "ordernow" , $order_button) ) ; 

pri nt end_form( ) ; 

include "end_page. php" ; 

?> 



Chapter 14: Shopping Cart 401 



Summary 



In this, the final application of the book, you've seen a few interesting things. 
You've learned that an application like a shopping cart requires some method of 
maintaining state. Probably the best way to maintain state with PHP for something 
like a shopping cart is with sessions. 

If you wish to process credit cards, you will need a secure server, an SSL certifi- 
cate, and a set of functions for processing cards. In this application we used the 
cURL functions for credit card processing, but there are two other function sets 
(PayfloPro and Cybercash) that you should look into. 

The final thing to note in this chapter is how classes were used. By extending 
existing classes, and writing methods to overwrite previous methods, we were able 
to flexibly use large blocks of code. 



Appendix A 

HTM L Forms 



If you want your applications to take user data, you are going to need a place for 
them to enter the information. That requires HTML forms. HTML forms are easy 
enough to work with. There are several commonly used input types, and in 
browsers that make use of HTML 4.0 and Cascading Style Sheet there are some 
techniques that you can use to make your forms a bit fancier. A full discussion of 
everything you can do with forms is beyond the scope of this book. If you need 
more information on forms and how they can work with CSS or JavaScript, or 
some of the newer browser-specific form types, check out the documentation at 

http://microsoft.com or http://moz ilia. org. 



Form Basics 



Each form is delimited by opening and closing <form> tags. The <form> tag takes 
the following attributes: 

♦ action— This attribute specifies the URL of the page that a form will be 
sent to for processing. It can contain a relative URL (e.g., "myscript.php" or 
"../myf older/my script") or a completeURL (e.g., http://www.mydomain/ 
myscri pt .php"). 

♦ method— This attribute indicates the HTTP request type the browser will 
send to the server. It must be set to either GET or POST If you set it to 
GET, the name=value pairs will appear in the browser location bar (e.g., 

http://mypage.com?namel=valuel&name2=value2). The advantage of 

using GET is that results can be bookmarked in the browser. The disad- 
vantage is that the variables you send will be more transparent. If you 
set this attribute to POST the name=value pairs will not be visible. The 
default value is GET. 

♦ name— This attribute is most useful for addressing portions of a form 
through J avaScript. The form name is not sent to the server when the 
form is submitted. 

♦ enctype —The default is "application-x-www-form-urlencoded", and 
this will normally be fine. But if you are uploading files (using <i nput 
type="fiie">, you should use "multipart/form-data". 



405 



406 



Part V: Appendixes 



A typical form shell will look something like this: 

<form name="myform" acti on="processor . php" method="post"> 
</form> 



Input Types 



Most of the work in your forms will be done by the input types. An input tag and 
the type attribute will determine what kind of form element is rendered in your 
browser. Every input type must have a name attribute. That's how you're going to 
pass variables to your scripts, so make sure you don't forget them. (To be absolutely 
accurate, you don't need to supply name attributes to submit and reset buttons.) 

As a quick example, the following would create a simple form with a single text 
box and a submit button. The text box has the default value of "hello there", as shown 
in Figure A-l. 

<form> 

<input type="text" size="50" maxl ength="15" 
value="hello there"Xbr> 

<input type="submi t" name="submi t" value="0K?"> 
</form> 



% D:\junk\form1.html 



M 



J File Edit View Favorites lools Help 



Address |g D:\iunk\form1.t 



"3J &G° J Links 



|hello the 

I OK? I 



ijastartpigftiQfflB^a Ule ±||[^U¥)°0'M ^■/ s t--o j B 



6:46 AM 



Figure A-l: Simple HTML form 



Appendix A: HTML Forms 407 

The input types are as follows. Note that different input types have different 
attributes associated with them. Each of them takes a name attribute. 

♦ Text— This type is shown in the above example. It can take these attributes: 

■ Size, which indicates the length of the box rendered in the Web browser. 

■ Maxlength, which limits the number of characters that can be input 
into the field. Keep in mind that older browsers will ignore maxlength, 
and even in newer browsers, you should not rely on this attribute to 
limit uploads. Check your upload_max_filesize item in your php.ini 

to set the limit on the server. 

■ Value, which is the default value in the box. The user can override it 
by typing in different information. 

Password —This type is identical to the text field, except that the text 
that is typed into the box is shown as asterisks. 



♦ 



♦ Hidden —This type does not render on the screen. It is very useful for 
passing values between pages. The name and value attributes are all 
you need with hidden fields. Consider using hidden fields if you're 
uncomfortable with cookies or sessions. Note that by simply viewing 
the source of your Web page, a savvy user will be able to see your 
hidden form elements. Do not put any sensitive data in hidden form. 

♦ Submit— This type places a submit button on the page. The text in the 
value attribute will appear on the submit button. When the form is sub- 
mitted, the name and value of the submit button are passed along like 
all other form elements. 

♦ Image— This type will serve the same purpose as the submit button, but it 
will allow you to specify an image to use instead of that ugly old submit 
button. Treat this form element as you would any <img> tag. Provide both 
scr and alt attributes. 

♦ Reset— This type provides a button that, when pressed, alters your form 
to the state it was in when the page was initially loaded. The default text 
on the reset button is "Reset". By adding a value attribute, you can change 
the text on the reset button. A Reset button does not involve PHP or the 
server in any way. 

♦ File— This type gives what looks like a textbox and a button with the 
text "browse" on it. When users hit browse, they are given a window 
that allows them to go through their operating system to find the file 
they would like to upload. If using this input type, be sure to change the 
form enctype attribute to "multipart/form-data". See Chapter 10 for a 
discussion of file uploads with PHP. 



408 Part V: Appendixes 



♦ Checkbox —The name and value of the checkbox will only be passed if the 
checkbox is checked when the form is submitted. If the word "checked" 
appears in the tag, the checkbox will be checked by default. Remember 

to use name=box_name[] to pass multiple checkboxes as an array. See 
Chapter 4 for a discussion of passing arrays with PHP. 

♦ Radio —Radio buttons allow the user to select only one of several choices. 
Radio buttons with the same name attribute belong to the same group. 
The "checked" command signifies the default choice. 

The following form makes use of most of the form elements we just covered, 
except for the image type. Figure A-2 shows how it is rendered in the browser. 

<h2>Please Enter Personal Inf ormati on</h2> 
<form> 

<input type="text" size="25" maxl ength="15" name="name" 
value="Name Here"Xbr> 

<input type="password" size="25" maxl ength="15" name="password" 
val ue=" "><br> 

<input type="hidden" value="you can't see me"> 

<input type="checkbox" name="tel emmarket" value="yes" checkedMf 
checked, I have permission to clear out your bank account. 

<P> 

<b>What is your eye col or?</b><br> 

<input type="radio" name="eye_col or" value="blue" 

checked>bl ue<br> 

<input type="radio" name="eye_col or" val ue="green">green<br> 
<input type="radio" name="eye_col or" val ue="brown">brown<br> 
<input type="radio" name="eye_col or" val ue=" red">red<br> 
<input type="submi t" name="submi t" vaul e="submi t"> &nbsp &nbsp 

&nbsp 

<input type="Reset"> 

</form> 



Select, multiple select 



The select form element creates drop-down boxes and (to use the Visual Basic term) 
list boxes. To create drop-down boxes, you must have an opening <seiect> tag 
with a name attribute. Within the select element, <option> tags will indicate pos- 
sible choices. Each of these will have a value attribute. 



Appendix A: HTML Forms 



409 



■=? D:\junk\form2.html - Microsoft Internet Explorer 



Mi 



J File Edit View Favorites lools Help 



Address |g D:\junk\form2.html 



73 &G° 



Links : 



Please Enter Personal Information 



|Name He 



r 

E If checked, I have permission to clear out your bank account. 

What is your eye color? 

& blue 

* green 

* brown 

Ored ^^^ 

SubrnitQuery Reset 



J 



a««t| fifty [flBasd |a ^j 3Vff)o0'^f Jyffc^g 



6:48 AM 



Figure A- 2: More form elements 

The following HTML creates a drop-down box with 3 elements. 

<form name="tester" acti on="scri pt . php" method="get"> 
<select name="di nner"> 

<option val ue="l">chi cken 
<option val ue="2">f i sh 
<option val ue="3">vegetari an 
</sel ect> 
</f orm> 

By adding the word "multiple" to the select element you enable the user to pick 
more than one of the choices. The size attribute determines how many of the 
options are visible at one time. 

The following code creates a list box with 3 visible elements. Figure A-3 shows 
how this HTM L looks in the browser. 



<form name="tester" acti on="scri pt .php" method="get"> 
<select name="si de_di shes" multiple size=3> 
<option val ue="l">potato 
<option val ue="2">pasta 



410 Part V: Appendixes 



<option val ue="3">carrot 
<option val ue="4">cel ery 
<option val ue="5">mango 
</sel ect> 
</form> 

Textarea 

The textarea element creates a large block for text entry. Add a row and column 
attribute to specify the size of the box. Textarea is different from other form 
elements in that opening and closing tags surround the default text. For instance: 

<textarea name="mytext" rows="5" col ums="20">Here ' s the default 
text</textarea> 

Keep in mind that if you have spaces or hard returns between your <textarea> 
tags, those characters will be carried to the form element. 

Add the wrap attribute to change how text wraps when it reaches the end of a 
line in the box. If the value is wrap=physical, carriage returns are added at the 
end of line; if the value is wrap=virtual, the lines will appear to wrap but will be 
submitted as a single line. This is almost always the best choice. 

These attributes came about from the folks at Netscape, and you still may need 
to use them. The official W3C HTML 4.0 attribute values for wrap are none, hard, 
and soft. 

Figure A-3 adds the select, multiple select and textarea elements to a form with 
this code. 

<h2>Please Enter Personal Inf ormati on</h2> 
<form> 

<fieldset i d="f i el dsetl" 
style="postion:absolute; 
width:300; 
hei ght : 100 ; 
top:20; 
left: 10;" 
> 
<legend>Food Questi ons</l egend> 
<b>What did you eat for dinner?</b><br> 
<select name="di nner"> 

<option val ue="l">chi cken 
<option val ue="2">f i sh 
<option val ue="3">vegetari an 
</sel ect><br> 



Appendix A: HTML Forms 



411 



<b>Any Side di shes?</b><br> 

<select name="si de_di shes" multiple size=3> 

<option val ue="l">potato 

<option val ue="2">pasta 

<option val ue="3">carrot 

<option val ue="4">cel ery 

<option val ue="5">mango 
</sel ect> 
<br> 

<b>How are you feeling about di nner?</b><br> 
<textarea name="mytext" rows="5" col ums="20"> 
Here's the default text</textarea> 
</fieldset> 

<P> 
<button> 

<img src="di sk. gi f " width="32" h e i g h t = " 3 2 " border="0" 
alt="disk"Xbr> 

Pretty Little Button 
</button> 
</f orm> 



■3 D:\from brad\book\form3.html - Microsoft Internet Explorer 



J File Edit View Favorites lools Help 



ilB^; 



\horn bfad^ook'\!Oirn3.Nmi 



~3J &Go J Links 






Please Enter Personal Information 



r Food Questions — 
What did you eat for dinner? 
| chicken j^j 
Any Side dishes? 



potato _H 
pasta --J 
carrot _^J 
How are you feeling about dinner? 



Here's the default I | 

text 



J 




zi 



;ast 3 rt| ^^^^■^l^| jj gjjg|a^||llUgt»0'M J./t'Ogj /:04AM 



Figure A- 3: Additional form elements 



412 Part V: Appendixes 

Other attributes 

With HTML 4.0 and the newest browsers some additional attributes have been 
introduced. M ake sure to test these as part of your QA process because they will not 
work on all browsers. 

ACCESSKEY 

An accesskey is the same as a hotkey. If this attribute appears in a form element, the 
user can hit (on a PC) Alt and the designated key to be brought directly to that form 
element. The hotkey is generally indicated by underlining of the hot letter. 

<input type="text" name="mytext" acceskey="m"Xu>M</u>y text box. 

TAB INDEX 

Users can use the Tab key to move through form elements. The tabindex attribute 
specifies the order in which focus will move through form elements. 

Other elements 

Internet Explorer 5 and Mozilla support a couple of new and seldom-used form 
elements that you might want to consider using. 

BUTTON 

The button is a fancier version of the submit button. It allows for both text and an 
image to be put on the same button. There are opening and closing <button> tags, 
and everything inside of them appears on the button. Figure A-3 shows an example 
of the button. 

FIELDSETAND LEGEND 

These are nice for grouping elements in forms. All text and tags within the <f iel dset> 
tags will be surrounded by a thin line. Text within the <1 egend> tags will serve as the 
caption for that grouping. 

Figure A-3 shows all of the form types. 

In the year 2001, it is still not a great idea to use most of the HTML 4.0 form 
elements and attributes. Generally speaking, they add very little, and they may look 
very strange on many browsers. 



Appendix B 

Brief Guide to PHP/MySQL 
Installation and 
Configuration 

When installing MySQL and PHP, you are faced with all kinds of options. The 
variety of operating systems and installation options creates more permutations 
than could possibly be handled in this book. Luckily, installation procedures for 
both packages are documented well in each package's respective documentation. 

In this appendix we will cover the basic installation and configuration of MySQL 
and PHP on Windows 98 and Unix systems. I'm assuming that you will be using 
the Apache Web server on all platforms. For the Unix installation, this book will 
document only the method of compiling the source code. If you wish to use RPMs 
for your installation, you should consult the online manuals. 

Windows 98 Installation 

Start by copying the MySQL binaries from the CD-ROM accompanying this book or 
the mysqi .com site to your local drive. Do the same for Apache and PHP (the 
appropriate download sites here are http://www.apache.org/dist and http:// 
www.php.net/downioad). The names of the files will be something like the follow- 
ing (they may be slightly different, depending on the version you are using): 

♦ mysql-3.23.22-beta-win.zip 

♦ apache_l_3_9_win32.exe 

♦ php-4.rj.lpl2-Win32.zip 

Start by unzipping themysql file and php files with your favorite unzip tool. (If you 
don't have one, we recommend Winzip, at http://www.winzip.com/.) Unzip them 
into a directory you find convenient. We prefer using a separate directory for each. 

Start with MySQL. In the directory where you unzipped the file, you will have a 
file named setup.exe. Execute that file. Choose a directory (e.g. d:\mysqlinstall) 
where you want it installed, and then in the next screen select a Typical installation. 
(You may wish to examine the custom options, but with the Windows install there 
are very few real options.) 413 



414 



Part V: Appendixes 



At this point your MySQL installation is complete. To test it, go to the DOS 
prompt and move to the directory you specified for your MySQL installation. Then 
move to the subcategory named \bin. If you then type mysqld, the mysql daemon 
should start. To test if your daemon is working, start up the mysql command-line 
client by typing mysql. If the monitor starts up and looks like Figure B-l, MySQL is 
working properly. 



M m is ® □ 


PS MYSQL 


■BE3 


1* e K i3_j nj in, taj jfjs aJ 


Microsoft(R) windows 98 

(^copyright Microsoft Corp 1981-1998. 




^ 


C:\WlNDOWS>d: 






D:\mysq1install\binxnysqld 






D:\mysq1install\binxnysql -u root 

welcome to the MySQL monitor, commands end with ; or \g. 

Your MySQL connection id is 1 to server version: 3. 23. 22-beta-debug 






Type 'help' for help. 






mysql> show databases; 
+ + 






+ + 

| mysql 

1 test | 

+ + 






mysql > 




- 


<i 


J > 










iastaiti evmmm&Qa \m M ^ii^w®^^^^** 


9:42 AM 



Figure B-l: MySQL monitor running on Windows 

Next, you should install Apache. 

This requires little more than double-clicking on the executable you copied from 
the CD or the apache.org site. The installation is pretty easy: all you really need 
to do is select a directory where you would like Apache to be installed. When it's 
completed, an Apache Group item will be added to the Start menu. 




Don't start up Apache just yet.A little more configuration information 
will follow. 



Appendix B: Brief Guide to PHP/MySQL Installation and Configuration 415 

Now on to PHP. You should have a folder into which you unzipped all the PHP 
files. In that folder copy MSVCRT.DLL and PHP4TS.DLL to c:\windows\system. Then 
rename php.ini-dist to php.ini and keep it in the same directory were you have the 
php.exefile. 

All you need to do at this point is make sure that Apache is aware of PHP and 
that PHP is aware of MySQL. 

First go to the directory where you installed Apache, find the httpd.conf file within 
the\conf directory, and open it in a text editor. Add these three lines to the file: 

ScriptAlias /php4/ "d:/php47" 

AddType appl i cati on/x-httpd-php4 .php 

Action appl i cati on/x-httpd-php4 "/php4/php .exe" 

Note that we indicated thed: drive because that's how we set up our own system. 
The c: drive will work just as well. 

The first line indicates the path where PHP resides. The second tells Apache what 
file extensions must be parsed as PHP, and the third gives the path to the php 
executable file. Note using this type of installation, PHP will run as an executable, 
not an Apache server module. 

If you would like other file extensions to by parsed by PHP, simply add another 
AddType line to the conf file; for example: 

AddType appl i cati on/x-httpd-php4 .phtml 

There are a couple of other alterations you may have to make to your httpd.conf 
file. If the server refuses to start, you may need to add something to the Server 
Name directive. If you are using TCP/IP in your local area network, you may need 
to add the IP address of your machine, for instance: 

ServerName 192.168.1.2 

Or if your machine is not networked, you may want to user the following 
ServerName 

ServerName 127.0.0.1 

If you also have Personal Web Server running on your machine, you may wish 
to change the port on which Apache runs. By default, Web servers listen on Port 80, 
but you can change that by altering the Port line in the httpd.conf to something 
else— perhaps 8080. 

And that should do it. Start Apache through the Start Menu. Add a file to your 
\htdocs folder that contains the phpinfo( ) function. When you call that function 
you should see that everything is working properly, and that there is an entry for 
MySQL. Figure B-2 shows the results of phpinfo(). 



416 



Part V: Appendixes 



-3 phpinfofl - Microsoft Internet Explorer 



File Edit View Favorites lools Help 



Address |© http: /Al 92. 1 68. 1 . 2: 8080/info. php 



~ZJ <^ G ° 



4" . "► . © E ffl 

Back Stop Refresh Home 



© a 

Search Favorites History 



Mail 



Print 



Edit 



PHP Version 4.0.2 



L 




i 



System 


Windows 95/98 4.10 


Build Date 


Aug 29 2000 


Server API 


CGI 


Virtual Directory Support 


enabled 


Configuration File (php.ini) Path 


php.ini 


ZEND_DEBUG 


disabled 


Thread Safety 


enabled 



iThiQ ni-nm-am rnakpQ iiqp nfthp 7pmH q r ri nt i n n lanniian 

±l 



J 



;astart| *> Q m ia 'A 4i ^M\ KfrH ^'ffX>-i J^t-^a 9:5 3am 



Figure B-2: phpinfo() on Windows 

Note that you don't need to make any alterations in the php.ini file to make PHP 
work with MySQL. MySQL support is, in fact, built into PHP for Windows. 




If you uncommentthe directive extension =php_mysq 1. 1 
kinds of problems getting a PHP page to load. 



,you will have all 



These are the basics you need to get going with PHP and MySQL on Windows. 
Note that you can also install PHP as an ISAPI filter for Internet Information Server 
(IIS) and PWS. The instructions for doing so are included in the readme.txt file 
included in the PHP zip file. As of this writing, running PHP as an IIS filter is not 
recommended in a production environment. 



Installation on Unix 



On Unix, there are far more options you may wish to avail yourself of. You may 
wish to install by compiling the source code yourself or (on Linux) by using rpm 
files. This appendix will only cover compiling from source. We strongly recommend 



Appendix B: Brief Guide to PHP/MySQL Installation and Configuration 417 

that you do not use rpm files. The convenience that rpms sometimes offer does not 
extend to this type of configuration. 

There are a variety of libraries and optional functions that you can compile into 
PHP, and additional libraries and functions are being added all the time. In this 
quick guide, we will cover only some highlights. 

If you have other priorities, need rpms, or wish to include options not covered 
here, seek out the documentation in the online manuals. This really isn't a very 
difficult install procedure, and you should be able to customize as you see fit with 
minimal effort. First stop, MySQL. 

MySQL Installation 

Complete information on MySQL installation can be found in Chapter 4 of the MySQL 
online manual: http: //www.mysql . com/documentati on /my sql /by chapter/ma nual_ 
install ing.html. Check it out if you are having problems. 

You will need to get the .tar.gz file either from the accompanying CD-ROM or 
from http://www.mysq! . com/ down l oads/. Copy it to a directory you wish to work 
in and then unpack this file with the following command: 

gunzip my sql -3. 23. 22. tar.gz 
tar xf mysql -3. 23. 22. tar 

This will create a directory with the name of the mysql distribution (for instance 
mysql-3.23.22). Use cd to move into the directory. Note that the exact version may 
be different, depending on the when you download the software. 

The first step is to run configure. There are many options you can set with config- 
ure flags. To get a complete list run ./configure --help. 

In the installations I've run, I've found it convenient to specify the --prefix. 
If you do not specify a prefix, /usr/local will be used, and this is almost always 
perfectly fine. Additionally, mysql allows you to specify the location of any of the 
subdirectories (the data directory, the bin directory, etc). Usually that will not be 
necessary. Normally you can run 

./configure --prefix=/path/to/installation 

If you need to make use of database transactions in your applications, you will 
need to make use of the Berkeley Database (BDB) tables. At the time of this writing, 
this feature is still in beta release. You can get the BDB tables from http:// 

www.mysql .com/downloads. 

If you want to use BDB tables you would run the following configure line, not 
the one shown previously. 

./configure --prefix=/path/to/installation --with-berkeley-db=/path/ 
t o / f i 1 e s 



418 Part V: Appendixes 



Next, run the following two commands: 

make 

make install 

That's it. You should now have a directory that contains all of your files and 
subfolders. 

The next thing you want to do is cd into the bin directory and run the following 
command: 

. /mysql_i nstal l_db 

This creates your default databases and permissions tables. You should now be able 
to start the mysql daemon using the safe_mysql command from the /bin directory. 

By default, mysql uses port 3306 and keeps the all important socket file at /tmp/ 
myslq.sock. This is generally OK. PHP will look for the socket file in this location. 
However, if you have multiple installations of MySQL you will need to change the 
port and socket location, which can be a pain. You will need to play with your 
my.cnf file. See Chapter 4 of the MySQL manual for more information. 



PHP/ Apache 



On Unix, PHP will be loaded as an Apache module. Thus the installation of the two 
will need to be done in concert. Once again, there are many, many installation 
options. You can create PHP as an executable for use with CGI or command-line 
processing, as a shared Apache module (apxs), or for DSO. Here we will only cover 
installation as an Apache module. 

Start by unpacking both Apache and PHP. 

gunzip apache 1.3.x.tar.gz 
tar xf apachel .3.x. tar 
gunzip php-4.02.tar.gz 
tar xf php-4.02 

Here "x" is the version number of apache. 

Use cd to move into the Apache directory and run configure, specifying the 
path where you would like Apache installed. 

./configure --pref ix= /path/to/apache 

This 'preps' Apache to set up machine-specific information that PHP needs to 
compile. You'll come back later and finish up the Apache installation. 



Appendix B: Brief Guide to PHP/MySQL Installation and Configuration 419 

Then move to the directory holding PHP. Here there are a variety of flags you 
may or may not wish to specify. We would suggest using the following: 

./configure --with-mysql=/path/to/mysql --enable-trans-id --enable- 
track-vars 

Here, the three flags do the following: 

♦ --with-mysql —You may know that client libraries for MySQL will be auto- 
matically installed even if you don't specify --with-mysql. However, if you 
installed MySQL someplace other than /usr/local, you will need to use this 
flag and specify the directory. It's good practice to specify this anyway. 

♦ --enable-track-vars— If this flag exists, variables from GET, POST, and 
COOKIES will be available in the appropriate arrays, HTTP_GET_VARS, 
HTTP_POST_VARS, HTTP_COOKIE_VARS. 

♦ --enabie-trans-sid— This option allows for Session ID to be included 
automatically in URLs after a session is started. The Shopping Cart in 
Chapter 10 makes use of this option. 

Additionally, you may wish to include one or more of the following flags: 

♦ --with-gd=path/to/gd— The GD functions allow you to create images 
on the fly, using nothing but code. GD requires a library from http:// 

www.boutell .com/gd/. 

♦ -- wi t h -con fig-file-path = /path/ to/file— The php.ini file specifies 
many options for the PHP environment. PHP expects to find the file in 
/usr/local/li b/php.i ni . If you wish to change the location, use this flag. 

♦ --with -curl /with-pfpro/with-cybyercash— If you wish to use any 

of these libraries to process credit-card transactions, you will need to 
download the appropriate library and specify the location of that library 
with a path, for example, --with-pfpro--path/to/pfpro. 

There are many other flags and libraries that you can incorporate into PHP. 
Please see the online manual (http://www.php.net/manual /instal l -unix.php) 
or run --configure --help for the latest and most complete list. 

After running configure, run the following two commands: 

make 

make install 



420 



Part V: Appendixes 



Now you will need to go back to the Apache directory and rerun the configure 
command: 

./configure --pref ix=/path/to/apache --acti vate-modul e= 
src/modules/php4/libphp4.a 

Note that the Iibphp4.a will not yet exist. It will be created after the compiling is 
completed. 

Now it's time to run the great twosome. 

make 

make install 

Apache should now be installed in the directory you specified. 

Now move back to the PHP directory and copy the file named php-ini.dist 
to /usr/local/li b/php.i ni (or to the directory you specified in the --config-file- 
path flag). 

The final step is to go into the /conf directory of your Apache installation and 
open the httpd.conf file. There you should uncomment the following line: 

AddType appl i cati on/x-httpd-php . php 

Then move into the /bin directory and start Apache. 

./apachectl start 

Your installation should now be complete. 




PHP will look for the socket to MySQL in /tmp/mysql.sock. If you have more 
than one MySQL installation and need PHP to connect to the socket in 
another location, you will need to specify that in the mysql_connect( ) 
function. 

my sql _c onnect("local host: /path/to/ my sql. sock", 
"username", "password"); 



PHP Configuration 



The php.ini file is extremely large, and has more options than we can cover here. A 
full list of options and potential settings can be found at http://www.php.net/ 
manual /conf i gurati on . php. Here are a few highlights. 



Appendix B: Brief Guide to PHP/MySQL Installation and Configuration 421 

MySQL configuration entries 

The following are some of the MySQL configuration entries. 

my sql . al 1 ow_persi stent 
mysql .max_persi stent 
mysql .max_l inks 
mysql .defaul t_port 
mysql .defaul t_host 
mysql . defaul t_user 
mysql .defaul t_password 

If you want to forbid persistent connections, change that setting to Off, or if you 
want to limit the number of persistent links to MySQL, change the setting on 
max_persi stent and max_l inks from -1 to an appropriate number. Persistent con- 
nections are explained in Chapter 6, in the discussion of the mysq1_pconnect() 
function. 

You can use the defaul t_user, defaul t_host, and defaul t_password entries if 
you want to save yourself the trouble of entering these strings in your mysql_ 
connect( ) command. Note that putting your MySQL password here is a probably a 
very bad idea. 

ERROR REPORTING 

This specifies the error reporting level. 

error_reporti ng = 

The default value here is 7, and generally that will be fine. The following is a list 
of the other potential values. If you indicate a number that is the sum of any of 
these values, all of the values that create the sum will be used. For instance 7 
includes 1, 2, and 4. 

1 = Normal errors 

2 = Normal warnings 
4 = Parser errors 

8 = Notices 

Note if you include 8, you will get a lot of messages, including things like unini- 
tialized. 

MAGIC QUOTES 

magi c_quotes_gpc 
magi c_quotes_runtime 



422 Part V: Appendixes 



If the first is set to On, all single quotes ('), double quotes ("), backslashes (\) and 
NULLs will be prepended with a backslash immediately upon being uploaded from 
a form element. This will make doing your inserts into MySQL a lot easier. 

If set to On, data retrieved from thefilesystem or a database will automatically be 
escaped with backslashes. 

EXECUTION TIME 

max_executi on_time = 30 
memory_l imit = 8388608 

These settings are intended to protect you in the event of an infinite loop or an 
endlessly recursive function. All scripts will automatically be terminated if they 
reach either of these limits. If you want to have a script you expect to take more 
than 30 seconds, you can set the maximum execution time within a script with the 
set_time_l imit ( ) function. This can contain the number of seconds; if you wish 
to specify no limit, use set_time_l imit(0). 

AUTO PREPEND AND APPEND 

auto_prepend_f i 1 e 
auto_append_f i 1 e 

With these settings you can specify files that will automatically be included at 
the start and end of your php files. It may be useful for connection information or 
common headers. 

INCLUDE PATH 

i ncl ude_path 

This should contain a list of paths separated by colons (:). These paths will 
automatically be searched for every i ncl ude( ) and requi re( ). 

SESSION 

There are many session settings you may with to change. Here are a few of them: 

sessi on . save_handl er = files 

sessi on . save_path = /tmp 

sessi on . use_cookies = 1 

sessi on . auto_start = 

Appendix H contains a set of functions for using MySQL for session handling. If 
you wish to use it, you must set the session. save.handler to user. 

save_path indicates where in thefilesystem PHP will save session information. 

If use_cookies is set to 0, you must use another means of storing cookies, 
either by using <?=sid ?> or by configuring PHP--with-trans-sid 

Finally, if auto_start is set to 1, sessions will be started automatically on 
every page. 



Appendix C 

MySQL Utilities 



This appendix presents a brief overview of some of the MySQL administrative 
utilities. These are the tools that you'll use to build and maintain your databases. 
Whether or not you'll have access to them depends on the exact version number you 
are running. 

The best place to get the full details about the tools you have available to you is 
the Docs subdirectory of your local installation of MySQL. (Note: This is the install 
directory, not the data directory.) You can also check the online version of the 

MySQL documentation at http://www.mysq! .com/documentation/ 

But be warned —the online manuals always document the most recent version of 
MySQL, which at the time of this writing is the beta release 3.23. 

If you're running the production release, 3.22, many of the features you'll read 
about on the Web site won't be available to you. I've marked off version 3.23 enhance- 
ments in this document where I could. You can always find out just what your version 
of a tool supports by running it with the -help option (e.g., mysql --help). 



mysql 



This is the command-line interface to MySQL; it allows you to run any arbitrary SQL 
command, as well as the MySQL-specific commands like describe table. It's a tool 
you should get to know. You can use it to test out or debug queries for your code, cre- 
ate your database, create tables, add columns to existing tables— everything, really. It 
also has some batch- oriented options that make it handy to use in maintenance 
scripts, or as a quick no-frills reporting tool. 
Syntax: 

mysql [options] [database name] [<i nputf i 1 e] [>outputf i 1 e] 

If you just type mysql , you'll start the tool up, but you won't be anywhere. When 
you try to do anything that involves interaction with a database, you'll get this error: 

ERROR 1046: No Database Selected 



423 



424 Part V: Appendixes 



hel p 


(\h 


1 


(\h 


cl ear 


(\c 


connect 


(\r 


host 




edi t 


(\e 


exi t 


(\q 


go 


(\g 


ego 


(\G 


v e r t i c a 


Hy 


print 


(\p 


quit 


(\q 


rehash 


(\# 


status 


(\s 


use 


(\u 


argumen' 


t 



To select one, type 

use databasename; 

use' is one of themysql tool's built-in commands. Type help to see a list of them: 

Display this text 

Synonym for ' hel p ' 

Clear command 

Reconnect to the server. Optional arguments are and 

Edit command with $EDIT0R 

Exit mysql . Same as quit 

Send command to mysql server 

Send command to mysql server; Display result 

Print current command 

Quit my s q 1 

Rebuild completion hash 

Get status information from the server 

Use another database. Takes database name as 



** new in 3.23: 

source (\.) Execute a SQL script file. Takes a file name as an 

argument 

Of course, it's simpler if you just give a database name on the command line. But 
this command does let you switch between databases in a single session. 

Once you're in a database, you can run an SQL statement by typing it in, 
followed by a semicolon, 'go' (this may not work by default in 3.23 installations) 
'\g', or '\G', and hitting return/enter. 

Table C-l list some of the more useful command-line options. 



Table C- 1 COM M ON MYSQL COM M AND- LINE CLIENT OPTIONS, PART I 

Flag Alternate Flag Description 

-? --help Display this help and exit 

-B --batch Print results with a tab as separator, each row on a 

new line. Doesn't use history file. 

-D, --databases . Database to use; this is mainly useful in the my.cnf file, 

to specify a default database. 



Appendix C: MySQL Utilities 425 

Flag Alternate Flag Description 

e --execute-. . . Execute command and quit. (Output like with --batch) 

-E --vertical Print the output of a query (rows) vertically. Without 

this option you can also force this output by ending 
your statements with \G. 



Vertical means that each field of each row is on a line by itself. For instance, 
looking at the STATUS table from the problem-tracking example, here is a standard 
query and output: 

mysql> select * from status 

-> go 
+ + + 

| status_id | status 

_i 1_ + 

| 1 | Opened | 

2 | In Progress | 

3 | Closed 

4 | Re-opened 

h h + 

Here is the same query with vertical output: 

mysql> select * from status 

-> go 
*************************** ]_ now *************************** 

status_id: 1 

status: Opened 

*************************** 2 row *************************** 

status_id: 2 

status: In Progress 

*************************** 3 row *************************** 

status_id: 3 

status: Closed 

*************************** 4 row *************************** 

status_id: 4 
status: Re-opened 

If you're feeding the output of your queries to another program for processing, 
like a Perl script, this form can be much easier to parse. 

Tables C-2 and C-3 list additional mysql command-line client options. 



426 Part V: Appendixes 



Flag 






Alternate Flag 


-f 






--force 


-h 






--host=. . . 


-H 






--html 


-L 






--skip-1 i ne- numbers 


-n 






--unbuffered 


-p[p< 


asswor 


•d] 


--password[=. . . ] 



Table C-2 COMMON MYSQL COMMAND- LINE CLIENT OPTIONS, PART II 



Description 

Continue even if we get a SQL error. 

Connect to the given host. 

Produce HTML output. 

Don't write line number for errors. 
Useful when one wants to compare 
result files that include error messages. 

Flush buffer after each query. 

Password to use when connecting to 
server. If password is not given on the 
command line, you will be prompted 
for it. Note that if you use the short 
form (-p) you can't have a space 
between the option and the password. 



Examples: 

mysql -u root -pfoobar 

or 

mysql --username=root --password=foobar 

Table C-3 COMMON MYSQL COMMAND- LINE CLIENT OPTIONS, PART III 

Flag Alternate Flag Description 

-P --port=... TCP/IP port number to use for connection. 

-q --quick Don't cache result, print it row by row. This may 

slow down the server if the output is suspended. 
Doesn't use history file. If you have problems 
due to insufficient memory in the client, use 
this option. It forces mysql to use mysql _ 
use_resul t( ) rather than mysql _store_ 
resul t( ) to retrieve the result set. 



Appendix C: MySQL Utilities 427 



Flag Alternate Flag Description 

-r --raw Write column values without escape conversion. 

Used with '--batch' 

--safe-mode Sends the following command to the MySQL 

(new to 3.23) server when opening the connection: 

SET SQL_SAFE_UPDATES=1,SQL_SELECT_ 
LIMIT=#select_limit#, 
SQL_MAX_JOIN_SIZE=#max_join_size#" 
where *#select_l imit#' and V/max_join_ 
s i ze#' are variables that you can set from the 
mysql command line. 



The effect of the previous command is: 

♦ You are not allowed to do an UPDATE or DELETE if you don't have a key 
constraint in the WHERE part. One can however force an UPDATE/DELETE 
by using LIMIT: UPDATE table_name SET not_key_column='some value' 
WHERE not_key_column='some value' LIMIT 1; 

♦ All big results are automatically limited to #select_ limit* rows. 

♦ SELECTS that will probably need to examine more than #max_join_size 
row combinations will be aborted. 

Table C-4 lists additional mysql command-line client options. 



Table C-4 COMMON MYSQL COMMAND- LINE CLIENT OPTIONS, PART IV 
Flag 



Alternate Flag 


Description 


--table 


Output in table format. This is default in 




non- batch mode. 


--user=# 


User for login if not current user. 


--wait 


Wait and retry if connection is down instead 




of aborting. 



428 Part V: Appendixes 



mysqladmin 



This is the command-line utility for performing administrative tasks. 
Syntax: 

mysqladmin [OPTIONS] command command.... 

The mysqladmin commands are listed in Table C-5: 

Table C-5 MYSQLADMIN COMMANDS 

Command Description 

create databasename Create a new database. 

drop databasename Delete a database and all its tables. 

extended-status Gives an extended status message from the server. 

flush-hosts Flush all cached hosts. 

fl ush-logs Flush all logs. 

flush-tables Flush all tables. 

fl ush-privi leges Reload grant tables (same as reload). 

kill id, id,... Kill mysql threads. 

password newpassword Change old password to the string newpassword. 

ping Check if mysqld is alive. 

processl ist Show list of active threads in server. 

reload Reload grant tables. 

refresh Flush all tables and close and open logfiles. 

shutdown Take server down. 

status Give a short status message from the server. 

va ri abl es Print variables available. 

version Get version info from server. 

slave-start Start slave replication thread, 
(new to 3.23) 

si ave-stop Stop slave replication thread, 
(new to 3.23) 



Appendix C: MySQL Utilities 



429 



Each command can be shortened to its unique prefix. For example: 

mysqladmin proc stat 

+ h h h h h h h + 

| Id | User | Host | db | Command j Time | State | Info | 

+ h h h h h h h + 

| 6 | monty | localhost | | Processlist | 

+ H H h H h H H + 

Uptime: 10077 Threads: 1 Questions: 9 Slow queries: Opens: 6 
Flush tables: 1 Open tables: 2 Memory in use: 1092K Max memory 
used: 1116K 

Table C-6 shows the columns created by the mysqi admi n status command. 



Table C-6 COLUMNS CREATED BY MYSQLSADMIN STATUS COMMAND 



Column name 

Uptime 
Threads 
Questions 
Slow queries 
Opens 
Flush tables 
Open tables 
Memory in use 

Max memory used 



Description 

Number of seconds the MySQL server has been up. 

Number of active threads (clients). 

Number of questions from clients since mysqld was started 

Queries that have taken more than N long_query_time' seconds. 

How many tables 'mysqld' has opened. 

Number of "flush ...', "refresh' and 'reload' commands. 

Number of tables that are open now. 

M emory allocated directly by the mysqld code (only available 
when *MySQL* is compiled with --with- debug). 

M aximum memory allocated directly by the mysqld code 
(only available when MySQL is compiled with -with-debug). 



In MySQL 3.23, if you do mysl qadmin shutdown on a socket (in other words, on a 
computer where mysqld is running), mysqladmin will wait until the MySQL pid-file 
is removed to ensure that the mysqld server has stopped properly. 

Table C-7 presents some of the more useful or common command-line options. 



430 



Part V: Appendixes 



Table C- 7 


MYSQLSADMIN COMMAND- LINE OPTIONS 


Flag 




Alternate Flag 




Description 


-7 




--help 




Display help and exit. 


"# 




--debug=. . . 




Output debug log. Often this is d:t:o,filename. 


-f 




--force 




Don't ask for confirmation on drop database; 
with multiple commands, continue even if an 
error occurs. 


-h 




--host=# 




Connect to host. 


-p[.. 


.] 


--password[=. 


..] 


Password to use when connecting to server. 



-P 






--port=. . . 


-I 






--si eep=sec 


-r 






--rel at i ve 


-s 






--silent 


-t 






--timeout=. . . 


-w 






--wai t[ = retri es] 


-w 






--pipe 


(Window: 


sor 


ily) 




-E 






--vertical 



If password is not given on the command line, 
you will be prompted for it. Note that if you 
use the short form -p you can't have a space 
between the option and the password. 

Port number to use for connection. 

Execute commands again and again with a 
sleep between. 

Show difference between current and previous 
values when used with - i . Currently works 
only with extended- status. 

Silently exit if one can't connect to server. 

Timeout for connection to the mysqld server. 

Wait and retry if connection is down. 

Use named pipes to connect to server. 

Print output vertically. Is similar to 
-relative, but prints output vertically. 



mysqldump 



This is the command-line utility for dumping out schema information and data 
from your databases. This is what you would use to back up your database, or move 
it from one machine to another. 



Appendix C: MySQL Utilities 431 

The output from mysql dump will most commonly be a script of SQL commands, 
to create tables and then insert data into them, mysqldump can also create plain 
data files (i.e., a tab- delimited file) from your tables. 

Syntax: 

mysqldump [OPTIONS] database [tables] 

or, as of 3.23: 

mysqldump [OPTIONS] --databases [OPTIONS] DB1 [DB2 DB3...] 
mysqldump [OPTIONS] --al 1 -databases [OPTIONS] 

A typical use of mysqldump would look like this: 

mysqldump --opt mydatabase > backup-f i 1 e. sql 
You can read this back into MySQL with the mysql command-line tool: 

mysql mydatabase < backup-Ti 1 e . sql 
or, as of 3.23: 

mysql -e "source /patch-to-backup/backup-Ti 1 e . sql " mydatabase 
You could also use it to copy data from one MySQL server to another: 

mysqldump --opt mydatabase | mysql --host=remote-host mydatabase 
Table C-8 lists the command-line options: 

Table C-8 MYSQLDUMP COMMAND- LINE OPTIONS 

Flag Alternate Flag Description 

-? --help Display this help message and exit. 

-a --all Include all MySQL specific create 

options. 

--add-locks Add LOCK TABLES before and UNLOCK 

TABLE after each table dump (to get 
faster inserts into MySQL). 

Continued 



432 Part V: Appendixes 



Table C-8 MYSQLDUMP COMMAND- LINE OPTIONS (Continued) 

Flag Alternate Flag Description 

--add-drop-tabl e Add a DROP TABLE IF EXISTS statement 

before each CREATE TABLE statement. 

--all ow- keywords Allow creation of column names that 

are keywords. This option works by 
prefixing each column name with 
the table name. 

-c --complete-insert Use complete insert statements (with 

column names). 

--del ayed Insert rows with the INSERT DELAYED 

command. 

-F --flush-logs Flush log file in the MySQL server 

before starting the dump. 

-f --force Continue even if you get a SQL error 

during a table dump. 

-h --host=.. Dump data from the MySQL server 

on the named host. The default host 
islocalhost. 

-1 --lock-tables Lock all tables before starting the dump. 

-t --no-create-info Don't write table creation info. 

-d --no-data Don't write any row information for 

the table. 

--opt Same as -quick --add- drop- table 

- - add- locks - extended- i nsert 
--lock- tables. Should give you the 
fastest possible dump for reading 
into a MySQL server. 

-p[...] --password[=. . .] Password to use when connecting to 

server. If password is not given on the 
command line, you will be prompted 
for it. Note that if you use the short 
form "- p' you can't have a space 
between the option and the password. 

-P --port=port_num The TCP/IP port number to use for 

connecting to a host. 



Appendix C: MySQL Utilities 433 



Flag 


Alternate Flag 


Description 


-q 


--quick 


Don't buffer query, dump directly 
to stdout. 


-T path-to- 


--tab=path-to- 


For each table, creates a table 


some-directory, 


some-di rectory 


name.sql file containing the SQL 
CREATE commands, and a table_ 
name.txt file containing the data 
from the table. 



Here's an example of the -T flag on a Unix installation: 

mysqldump -T . tracking problems 

This will create two files in the current directory (that's what '.' means): 'problems. 
sql ' and 'problems.txt'. 'problems.sql' will have the SQL statements to create the 
'problems' table, 'problems.txt' is a tab-delimited (the default format) copy of the 
data now in the 'problems' table in the 'tracking' database. 



This only works if mysqldump is run on the same machine as the mysqld 
daemon. The format of the .txt file is made according to the -fields-xxx 
and -lines-xxx options. 




Table C-9 lists more mysqldump command-line options. 



Table C- 9 MORE MYSQLDUMP COMMAND- LINE OPTIONS 

Flag Alternate Flag Description 

--fields-terminated-by=. . . These options have 

- -fields -end osed-by=. . . the same meaning as 

--fields-optionally-enclosed-by=. . . the corresponding clauses 

--fields-escaped-by=. . . for the LOAD DATA INFILE 

--1 ines-terminated-by=. . . statement. 

Continued 



434 Part V: Appendixes 



Table C-9 MORE MYSQLDUMP COMMAND- LINE OPTIONS (Continued) 
Flag Alternate Flag Description 

-u user_name, 



--user= 


The MySQL user name to 


user_name 


use when connecting to 




the server. 


--where= 


Dump only selected 


' where- 


records; Note that 


con d i t i o n ' 


QUOTES are mandatory! 




Example: 




--where= 




user= ' jimf " 




" -wuserid>l" 




" -wuserid<l" 



Table C-10 displays the mysqldump command line options that are new to 
version 3.23. 



Table C-10 MYSQLDUMP COMMAND- LINE OPTIONS NEW IN VERSION 3.23 
Flag Alternate Flag Description 

-A 

--all -databases Dump all the databases. This is the same as 

--databases with all databases selected. 



-databases Dump several databases. Note the 

difference in usage; in this case no tables 
are given. All name arguments are regarded 
as database names. USE db_name; will be 
included in the output before each new 
database. 

-extended- insert Use the new multiline INSERT syntax. 



Appendix C: MySQL Utilities 435 



Flag Alternate Flag Description 

-n --no-create-db 'CREATE DATABASE IF NOT EXISTS 

db_name;' will not be put in the output. 
The above line will be added otherwise, if 
--databases or --all-databases option 
was given. 

--tables Overrides option -databases (-B). 

-0 net_buffer_length=#, where # < 24M 

When creating multi- row- insert statements 
(as with option --extended- insert or --opt), 
mysqldump will create rows up to net_ 
bufferjength length. If you increase this 
variable, you should also ensure that the 
max_allowed_ packet variable in the 
MySQL server is bigger than the net_ 
bufferjength. 



mysql import 



Themysqi import tool provides a command line interface to the ' load data in file 1 
sql statement. Most options to mysql import correspond directly to the same options 

to 'LOAD DATA INFILE'. 

Syntax: 

mysqlimport [options] database textfilel [textf il e2. . . . ] 

For each text file named on the command line, mysql import strips any extension 
from the filename and uses the result to determine which table to import the file's 
contents into. For example, files named patient.txt, patient.text, and patient would 
all be imported onto a table named patient. 

You typically use mysql import to bring data into a MySQL database from some 
other source— another DBMS, a spreadsheet, or the like. You can also use it together 
with the mysqldump tool. Take the example I gave earlier: 

mysqldump -T . tracking problems 



436 



Part V: Appendixes 



This created two files, problems.sql and problems.txt. To reload the problems 
table from these files, you could do as follows: 

mysql tracking <problems.sql 
mysql import tracking problems.txt 

Some of the most common or useful command-line options are shown in 
Table C- 11. 



Table C- 11 MYSQLIM PORT COMMAND- LINE OPTIONS 



Flag 


Alternate Flag 


-? 


- - hel p 


-d 


--del ete 


- -f i elds -terminated -by =. 




--fields-end osed-by=. . . 




--fields-optional ly- 




enclosed-by=. . . 




--fields-escaped-by=. . . 




--1 ines-terminated-by=. . 





-force 



-h host_name 



--host=host_name 



■1 ock-tabl es 



--1 ocal 



Description 

Display a help message and exit. 

Empty the table before importing 
the text file. 

These options have the same 
meaning as the corresponding 
clauses for 'LOAD DATA IN FILE'. 



Ignore errors. For example, if 
a table for a text file doesn't 
exist, continue processing any 
remaining files. Without '--force', 
mysqlimport exits if a table 
doesn't exist. 

I mport data to the M ySQL server 
on the named host. The default 
host is localhost'. 

Lock all tables for writing before 
processing any text files. This 
ensures that all tables are 
synchronized on the server. 

Read input files from the client. 
By default, text files are assumed 
to be on the server if you connect 
to 'localhost' (the default host). 



Appendix C: MySQL Utilities 437 



Flag Alternate Flag Description 

-p[...] --password[=. . .] Password to use when connecting 

to server. If password is not given 
on the command line, you will be 
prompted for it. Note that if you 
use the short form % - p' you can't 
have a space between the option 
and the password. 

-P port_num --port=port_num The TCP/IP port number to use for 

connecting to a host. (This is used 
for connections to hosts other 
than localhost', for which Unix 
sockets are used.) 

-I --ignore The --repl ace and --ignore 

-r --replace options control handling of input 

records that duplicate existing 
records on unique key values. If 
you specify --replace, new 
rows replace existing rows that 
have the same unique key value. If 
you specify --ignore, input rows 
that duplicate an existing row on 
a unique key value are skipped. If 
you don't specify either option, an 
error occurs when a duplicate key 
value is found, and the rest of the 
text file is ignored. 

-s --silent Silent mode. Write output only 

when errors occur. 

-u user_name --user=user_name The MySQL user name to use 

when connecting to the server. 
The default value is your Unix 
login name. 

-c --columns=. . . This option takes a comma- 

separated list of field names as an 
argument. The field list is passed 
to the LOAD DATA IN FILE MySQL 
sql command, which mysqlimport 
calls MySQL to execute. For more 
information, please see 'LOAD 
DATA IN FILE'. 



438 Part V: Appendixes 



Other Utilities 



Please check the /bin directory for other utilities that come with MySQL. If you are 
using 3.23 you will want to look at myisamchk and myisampack. The first repairs 
corrupted tables and the second will ensure that tables are set up as efficiently as 
possible. These utilities only work with the MylSAM tables. If you are using 3.22, 
you will need to use of the isamchk utility, which operates on the ISAM tables used 
in this version of MySQL. 



Appendix D 

MySQL User 
Administration 



This appendix will teach you to work with MySQL's grant tables, which control 
permissions in MySQL. 

Administration of any relational database management system (RDBMS) 
requires some work. Each system presents its own unique methods for administra- 
tion and difficulties when it comes to tasks like adding and deleting user accounts, 
backing up, and assuring security. Administering MySQL isn't especially difficult, 
but it can be a bit bewildering at first. 

This book focuses on applications development, not server administration. Thus 
extensive details on administration are beyond the scope of this tome. If you are 
responsible for backup and security of your server, you should delve deep into the 
MySQL online manual, focusing on Chapters 21 (for backup) and Chapter 6 (for 
security). 

For the purposes of this book, and we hope also for you, the application devel- 
oper, it is enough to know a bit about user administration and the methods for 
assigning rights for users. 



Grant Tables 



MySQL user rights are stored in a series of tables that are automatically created 
with the MySQL installation. These tables are kept in a database called mysql. If 
you start up the MySQL daemon (with mysql d) and the MySQL monitor (with 
mysql), and run the query show databases just after installation, you will see two 
databases, test and mysql. 

Running the show tables query on the mysql database lists the tables that store 
user permissions. 

mysql> use mysql 

Database changed 

mysql> show tables; 
_i + 

| Tables in mysql 
_i + 

| columns_priv 



439 



440 Part V: Appendixes 



db 

f unc 

host 

tables_priv 

user 
_i h 

6 rows in set (0.00 sec) 
mysql > 

Each of these tables corresponds to a level of access control. You can create any 
number of users, and users can be allowed access from any variety of hosts. For 
each user/host combination, you can grant access to an entire database, to specific 
tables within a database, or to a number of columns within a table. Additionally, 
these tables grant administrative privileges. Users can be given permission to add 
and drop databases or permission to grant other users permissions. 

In practice you will want to grant no more permissions than necessary. You want 
to protect your data from the overzealous and the incompetent. The best way to do 
that with MySQL is to use the proper grant table when assigning rights, keeping the 
following in mind: Rights are granted in a hierarchical way. Rights granted in the 
user table will be universal. If a user is granted drop privileges in the user table, that 
user will be able to drop any table in any database in that MySQL installation. 

Then there is the db table, which grants privileges on a database- specific basis. 
Using this table, you can grant rights for an entire database. For any one table or 
set of tables, make use of the tables_priv table. Finally, the columns_priv table 
allows you to grant rights on specific columns within a table. If you don't need to 
grant rights to an entire table, see that rights are assigned in the columns_priv 
table. 

Recent releases of MySQL make use of a couple of very convenient commands 
that make creating users and assigning rights fairly easy. I'll discuss these com- 
mands after a brief look at the user, db, tables_priv, and columns_priv tables. 

user table 

Every user who needs to get at MySQL must be listed in this table. Rights may be 
granted elsewhere, but without a listing here, the user will be refused a connection 
to the database server. Here is the listing of columns in the user table. 

mysql) show columns from user; 

_i 1 h h _| h h 

Field | Type | Null | Key | Default | Extra j 
_i h h h _| h h 

Host | char(60) | | PRI | | | 

User | char(16) | j PRI j | j 

Password I char(16) II I 



Appendix D: MySQL User Administration 



441 



| Select_priv 




| enum( 


| Insert_priv 




enum( 


| Update_pri v 




| enum( 


| Delete_priv 




enum( 


| Create_priv 




| enum( 


| Drop_priv 




| enum( 


| Reload_priv 




| enum( 


| Shutdown_pri v 




| enum( 


| Process_priv 




| enum( 


| File_priv 




enum( 


| Grant_priv 




| enum( 


| References_pri v 


| enum( 


| Index_priv 




| enum( 


| Alter_priv 




enum( 

H 

00 sec) 


17 rows in set 


(0 



N' 


,'Y') | | | 


N' 


,'Y') j 


N' 


,'Y') 1 


N' 


,'Y') j 


N' 


,'Y') j 


N' 


,'Y') j 


N' 


,'Y') j 


N' 


,'Y') j 


N' 


,'Y') j 


N' 


,'Y') j 


N' 


,'Y') j 


N' 


,'Y') j 


N' 


,'Y') j 


N' 


,'Y') j 



_| 1_ _| 1_ |_ 



mysql > 

As you must have seen by now, the PHP mysqi_connect( ) function takes three 
arguments: username, host, and password. In the preceding code you will see the 
corresponding field names. MySQL identifies a user by the combination of the user- 
name and host. For instance, user jay can have a different set of rights for each host 
that he uses to connect to MySQL. If you or your PHP scripts are accessing MySQL 
from the local machine, you will usually assign a host of localhost. 

The other columns are intuitively named. As you can see, all but the Host, User, 
and Password columns allow only Y or N as column values. As we mentioned ear- 
lier, any of these rights that are set to Y will be granted to every table of every data- 
base. Most of the columns' names correspond to SQL statements (e.g. delete, create, 
and so forth). 

The user table also contains a set of columns that grant administrative rights. 
These columns are Filepriv, Grand_pirv, Process_priv, Reloadpriv, and 
Shutdown_priv. The following is a brief explanation of the meaning of these 
columns. If you are security- minded, grant these rights sparingly. 



Fi lepriv — If granted, this privilege allows the database server to read and 
write files from the file system. You will most often use it when loading a 
file into a database table. 

Grant_priv— A user with this right will be able to assign his privileges to 
other users. 

Process_priv— This right gives a user the ability to view and kill all run- 
ning processes and threads. 



442 



Part V: Appendixes 



♦ Reload_priv — M ost of the privileges granted by this column are not cov- 
ered in the course of this book. This privilege is most often used with the 
mysql admin utility to perform flush commands. Seethe MySQL online 
manual for more details. 

♦ Shutdown_priv— Allows the user to shut down the daemon using 

mysql admi n shutdown. 

db table 

For database- specific permissions, the db table is where you will be doing most of 
your work. The following is a list of columns from the db table: 



mysql) show columns from db; 



Field 

Host 

Db 

User 

Sel ect_pri v 

Insert_pri v 

Update_pri v 

Del ete_pri v 

Create_pri v 

Drop_pri v 

Gran t_p r i v 

References_ 

Index_pri v 

Al ter_pri v 



Type 



Nul 



Key 



h h 

Default [ Extra [ 
h h 



pn v 



| char(60 




I PRI 1 


char(32 




PRI 


| char( 16 




PRI 


| enum( 


N 


,'Y') 




| enum( 


N 


,'Y') 




| enum( 


N 


,'Y') 




| enum( 


N 


,'Y') 




| enum( 


N 


,'Y') 




| enum( 


N 


,'Y') 




| enum( 


N 


,'Y') 




| enum( 


N 


,'Y') 




| enum( 


N 


,'Y') 




| enum( 


N 


,'Y') 





-+- 



13 rows in set (0.01 sec) 



mysql > 



This works like the user table, except that permissions granted here will only 
work for the database specified in the db column. 



tablespriv and columns priv 



These two tables look pretty similar, and to save a bit of space, I'll only show the 
tables_priv table. 



mysql) show columns from tables_priv; 

_i h h h _| h 1_ 

Field | Type | Null | Key | Default | Extra [ 



Appendix D: MySQL User Administration 



443 



+ H H h H h h 



Host 


char(60) | 


PRI j 


Db 


char(60) | 


PRI | 


User 


char( 16) 


PRI ] 


Tabl e_name 


char(60) | 


PRI | 


Grantor 


char(77 ) 


MUL 


Timestamp 


timestamp(14) | YES 




Tabl e_pri v 


set( ' Sel ect ',' Insert ' , 


Update 



1ULL 



'Delete' , 'Create' , 'Drop' , 'Grant' 
'References' , ' Index' , 'Alter' ) 



Col umn_pri v set( ' Select ' , 'Insert', 
'Update' , 'References' ) 

h h h h-- 

8 rows in set (0.00 sec) 



— h 1 h 



For users who only get access to a table or set of tables within a database, the 
exact rights will be stored in this table. Note the use of the set column type for 
table_priv and column_priv tables. All of the rights available to a specific user will 
be crammed into these two cells. 




At a couple of points in the course of this book, we advised against using the 
set column type. In fact the db table is a good example of where set makes 
sense. There are few potential values for the column and the number of 
potential values is not likely to change. 



Grant and Revoke Statements 

Since the tables discussed above are regular MySQL tables, you can alter them with 
the SQL statements you are already familiar with. But consider the nightmare that 
would be. If you wanted to grant a new user table-level access, you would first need 
to insert a row into the user database with an SQL statement that looked like this: 

INSERT INTO user (Host, User, Password, Select_priv, Insert_priv, 
Update_priv, Delete_priv, Create_priv, Drop_priv, Reload_priv, 
Shutdown_pri v , Process_pri v , File_priv, Grant_priv, References_pri v , 
Index_priv, Alter_priv) VALUES ( ' 1 ocal host ' , 'juan', 'password', 



) 



444 



Part V: Appendixes 



Then you'd need to grant specific rights with another insert statement to another 
table. 

If you are thinking you could script these functions with a Web front end, that is 
definitely a possibility. But you'd want to be very careful, because the script would 
have the equivalent of root access to the database, which could be very unsafe. 

Happily, the MySQL has some built-in statements that make user administration 
a whole lot easier. Knowing the grant and revoke statements will save you from 
having to send individual queries. 

Grant 

Before we get into specifics of this statement, take a look at the statement that 
would grant all rights on the database named guestbook to user jim; Jim's password 
will be pword. 

mysql> grant all on guestbook.* to jim@l oca 1 host 
identified by "pword"; 

This command will make all the necessary changes to the user and db tables. 

The first part of the grant statement can take the word al l , or it can take any of 
the options listed in the user table. Most often, you will be granting rights to use 
SQL statements (select, create, alter, delete, drop, index, insert, and update). 

The second portion (on guestbook in the example) identifies where privileges 
will be applied: universally, to a single database, to tables, or to columns. Table 
D-l shows how to indicate where privileges should be applied. 



Table D- 1 PERM ISSION LEVEL 



Identifier 

grant all on *.* 

grant all on database* 

grant all on database.table_name 

grant allfcoll, col2) on database.table_name 



Meaning 

Universal rights; inserted into the user 
table 

Applies to all tables in a single 
database 

Rights apply to a single table 

Rights apply only to specific columns in 
a specific database and table. 



The third portion (to jim@iocai host in the example) indicates the user to be 
given access. As we mentioned earlier, MySQL needs both a name and a host. In the 
grant statement, these are separated by the @ symbol. 



Appendix D: MySQL User Administration 445 

Finally the identified by portion gives the user a password. 
Here are a few more examples of grant statements. 

grant select, update, insert on guestbook2k. guestbook to 
al vi n@l ocal host identified by "pword"; 

The preceding statement allows alvin to view, update, and insert records into the 
table guestbook in database guestbook2k. 

grant select, update (name, url ) on guestbook2k. guestbook to 
chi pmunk@l ocal host identified by "Mell2068"; 

With the preceding statement, the user can only view and update two columns 
(name and URL). No deletes or inserts allowed. 

grant all on *.* to josh@l ocal host identified by "pword"; 

The preceding statement gives this user all privileges. This means that josh® 
local host is even allowed to grant privileges to other users. 

Revoke 

If you want to remove some of a user's privileges, you can use the revoke state- 
ment. To remove shutdown privileges from a user who had been granted all privi- 
leges, like josh above, you could run the following: 

revoke Shutdown on *.* from josh@l ocal host ; 

Notice that the word from is used in the revoke statement in place of to. 
Otherwise revoke works just like grant. 

Note that to remove a user entirely you must run a delete statement against the 
user table. Since the user is identified by a name and host, the following should do it: 

delete from user where user='user' and host='host' 



Viewing grants 



Starting in version 3.23.4, MySQL incorporated the show grants statement, which 
allows you to see the exact grants available at a given time. All you need to know 
is the user name and host. 

mysql> show grants for jayg@l ocal host ; 

+ + 

| Grants for jayg@l ocal host 

+ + 



446 Part V: Appendixes 



GRANT ALL PRIVILEGES ON my_test.* TO ' jayg '@' 1 ocal host ' 
_i 1_ 

1 row in set (0.00 sec) 



Reloading grants 



The grant tables are loaded into memory when the MySQL daemon is started. 
Changes made to the grant tables that did not make use of the grant command will 
not take effect until you restart the program or tell MySQL to reload the table with 
the fl ush command. 
Simply run: 

flush privileges 



Appendix E 

PHP Function Reference 



At this point, PHP contains more functions than could possibly be listed in this 
book, and the function list is growing daily. Tables E-l to E-43 are lists of just some 
of the functions available. Keep up with the online manual at www.pnp.net/man- 
ual to see the most current list. 



Table E-l PHP INFORMATION FUNCTIONS 


Function Returns 


Action 




phpi nfo( voi d) void 


Outputs a page 


of useful information about PHP 




and the current request 


phpversion(void) string 


Returns the cui 


rent PHP version 


phpcredits(void) void 


Prints the list of people who've contributed to the 




PHP project 




Table E-2 VARIABLE TYPE FUNCTIONS 


Function 


Returns 


Action 


intval (mixed var [, int base]) 


int 


Gets the integer value of a 
variable using the optional base 
for the conversion 


doubl eval (mixed var) 


double 


Gets the double- precision value 
of a variable 


strval (mixed var) 


string 


Gets the string value of a 
variable 


gettype(mixed var) 


string 


Returns the type of the variable 
Continued 



447 



448 Part V: Appendixes 



Table E-2 VARIABLE TYPE FUNCTIONS (Continued) 



Function Returns 

settype( stri ng var, string type) int 



i s_bool (mixed var) 



i s_l ong( mixed var) 



i s_doubl etmixed var) 



i s_stri ngtmixed var) 



i s_array (mixed var) 



i s_object(mixed var) 



i s_numeri c(mixed value) 



Action 

Sets the type of the variable. 
Returns 1 if the conversion is 
successful 



bool 


Returns true if variable is a 




Boolean 


bool 


Returns true if variable is a 




long (integer) 


bool 


Returns true if variable is a 




double 


bool 


Returns true if variable is a 




string 


bool 


Returns true if variable is an 




array 


bool 


Returns true if variable is an 




object 


bool 


Returns true if value is a 




number or a numeric string 



Table E-3 QUOTE SETTING FUNCTIONS 



Function 



Returns Action 



set_magi c_quotes_ 

runtime(int new_setting) int 



get_magi c_quotes_ 
runtime( voi d) 

get_magi c_quotes_ 
g p c ( v o i d ) 



int 



int 



Sets the current active configuration 
setting of magic_quotes_runtime and 
returns the previous setting 

Gets the current active configuration 
setting of magic_quotes_runtime 

Gets the current active configuration 
setting of magic_quotes_gpc 



Appendix E: PHP Function Reference 



449 



Table E-4 DATETIME FUNCTIONS 



Function Returns 

time (void) int 

mktimeO'nt hour, int min, int 

int sec, int mon , int 
day, int yean) 

gmmktimed'nt houn, int int 

min, int sec, int mon, 
int day, int yean) 

date(stning format string 

[, int timestamp]) 

gmdate(stri ng format string 

[, int timestamp]) 

local time( [int timestamp array 

[, bool associative_ 
array]] ) 

getdate( [int timestamp]) array 

checkdate( i nt month, int bool 

day, int year) 

strftime(stri ng format string 

[, int timestamp]) 

gmstrftime( stri ng format string 

[, int timestamp]) 

strtotime( stri ng time, int 

int now) 



microti me (void) string 

gettimeofday ( void) array 

getrusage( [i nt who]) array 



Action 

Returns current UNIX timestamp 
Composes UNIX timestamp for a date 

Gets UNIX timestamp for a GMTdate 

Formats a time/date 

Formats a GMT/CUT date/time 

If the associativearray argument is 
set to 1, returns the results of the C 
system call localtime as an associative 
array; otherwise returns a regular array 

Gets date/time information 

Returns 1 if it is a valid date 

Formats a local time/date according 
to locale settings 

Formats a GMT/CUT time/date 
according to locale settings 

Converts string representation of 
date and time to a timestamp. Will 
accept strings in most typical date 
formats. For example, YYYY- M M - DD 
andMM/DD/YYYY 

Returns a string containing the current 
time in seconds and microseconds 

Returns the current time as array 

Returns an array of usage statistics 
taken from the getrusage Unix 
command. See your Unix man page 
for further details 



450 



Part V: Appendixes 



Table E-5 DIRECTORY FUNCTIONS 



Function 

opendir(string path) 

dir( string directory) 



Returns 

int 

class 



cl osedi r( [i nt dir_handle]) void 

chdir( string directory) int 

getcwd(void) string 

rewi nddi r( [i nt dir_handle]) void 

readdir([int dir_handle]) string 



Action 

Opens a directory and return a 
dirhandle 

Returns an object with three 
methods (read, rewind, and close) 
and two properties (handle and path) 

Closes directory connection 
identified by the dirhandle 

Changes the current directory 

Gets the current directory 

Rewinds dirhandle back to the start 

Readsdirectory entry from dirhandle 



Table E- 6 FILESYSTEM FUNCTIONS 



Function Returns 

di skf reespacetstri ng path) double 

chownt string filename, bool 
mixed user) 

chgrp(string filename, bool 
mixed group) 

chmodtstri ng filename, bool 
int mode) 

touch(string filename bool 
[, int time]) 

clearstatcache(void) void 

f i 1 eperms (stri ng filename) int 

f i 1 ei node(stri ng filename) int 

f i 1 esi ze(stri ng filename) int 



Action 

Gets free diskspace for filesystem 
that path is on, in bytes 

Changes file owner. Returns TRUE on 
success, otherwise FALSE 

Changes file group. Returns TRUE on 
success, otherwise FALSE 

Changes file mode. Retruns TRUE on 
success, otherwise FALSE 

Sets modification time of file 



Clears file stat cache 
Gets file permissions in octal 
Gets file inode 
Gets file size 



Appendix E: PHP Function Reference 



451 



Function 

f i 1 eowner( stri ng filename) 

filegroup(string filename) 

f i 1 eatime( stri ng filename) 

f i 1 emtime( stri ng filename) 

f i 1 ectime( stri ng filename) 

f i 1 etype(stri ng filename) 

i s_wri tabl e( string 
f i 1 ename) 

i s_readabl e(stri ng 
f i 1 ename) 

i s_executabl e( stri ng 
f i 1 ename) 

i s_f i 1 e(stri ng filename) 

i s_di r(stri ng filename) 

i s_l i nk(stri ng filename) 

f i 1 e_exi sts(stri ng 
f i 1 ename) 

lstat(string filename) 



Returns 


Action 


int 


Gets file owner's userid 


int 


Gets file's groupid 


int 


Gets last access time of file 


int 


Gets last modification time of file 


int 


Gets inode modification time of file 


string 


Gets file type 


int 


Returns true if file can be written 



stat(string filename) 



int Returns true if file can be read 

int Returns true if file is executable 

int Returns true if file is a regular file 

int Returns true if file is directory 

int Returns true if file is symbolic link 

bool Returns true if filename exists 

array Gives information about a file or 

symbolic link. The returned array 
contains the following elements: 
device, inode; inode protection mode; 
number of links; user id of owner; 
group id owner; device type if inode 
device; size in bytes; time of last 
access; time of last modification; 
time of last change; blocksize for 
filesystem I/O; number of blocks 
allocated 

array Gives information about a file, the 

same as described in Istat 



452 



Part V: Appendixes 



Table E-7 EXECUTION FUNCTION 



Function 

exec(string command 
[, array output [, int 
return_val ue]] ) 

system( stri ng command 
[, int return_val ue] ) 

passthru(stri ng command 
[, int return_val ue] ) 



escapeshellcmd( string 
command) 



Returns Action 

int Executes an external program 



int Executes an external program and 

displays output 

void Executes an external program and 

displays raw output. Will usually be 
used with something like PBM Plus 

string Escapes shell metacharacters 



Table E-8 FILE MANIPULATION FUNCTIONS 



Function Returns 

flockd'nt fp, int bool 

operati on 

[ , i nt woul dbl ock] ) 

get_meta_tags (stri ng array 

filename [, int use_ 
i ncl ude_path] ) 

file(string filename array 

[, int use_i ncl ude_path] ) 

tempnam( stri ng dir, string 

tmpf i 1 e( void) int 



fopen(string filename, int 

string mode [, int use_ 
i ncl ude_path] ) 



Action 

Locks a file so that it is not 
accessible by other PHP scripts. The 
locking will not keep other processes 
from opening the file 

Extracts all <meta> tag content 
attributes from a file and returns 
an array 

Reads entire file into an array, with 
each line as an array element 

Creates a unique filename in a 

string pref ix)directory 

Creates a temporary file that will be 
deleted automatically after a call the 
fclose( ) function or at the end of 
the script 

Opens a file or a URL and returns a 
file pointer 



Appendix E: PHP Function Reference 



453 



Function Returns 

fclosednt fp) int 

popen(string command, int 
string mode) 

pclose(intfp) int 



feofdnt fp) int 

set_socket_bl ocki ng( i nt int 

socket_descri ptor , 
int mode) 

socket_set_timeout( i nt bool 

socket_descri ptor , 
int seconds, int 
microseconds) 

socket_get_status ( resource array 
socket_descri ptor) 



fgetsdnt fp, int length) string 

fgetcdnt fp) string 

fgetssd'nt fp, int length string 
[, string al 1 owabl e_tags] ) 

f scant (stri ng str, string mixed 
format [ , stri ng . . . ] ) 

fwritednt fp, string str int 
[, int length]) 

fflushdnt fp) int 

set_f i 1 e_buf fer( i nt fp, int 
int buffer) 

rewindd'nt fp) int 

ftell (int fp) int 



Action 

Closes an open file pointer 

Executes a command and opens 
either a read or a write pipe to it 

Closes a file pointer opened by 

popen( ) 

Tests for end- of-file on a file pointer 

Sets blocking/non- blocking mode on 
a socket 



Sets timeout on socket read to 
seconds + microseconds 



Returns an array describing socket 
status. The array contains four 
elements: timedout (bool), blocked 
(bool), eof (bool), and unread bytes 
(int) 

Gets a line from file pointer 

Gets a character from file pointer 

Gets a line from file pointer and 
strips HTML tags 

Formats a file using the specified 
format 

Implements a binary- safe file write 

Flushes output 

Sets file write buffer; the default size 
is8kb 

M oves the position of a file pointer 
to the beginning of a file 

Gets file pointer's read/write position 

Continued 



454 



Part V: Appendixes 



Table E-8 FILE MANIPULATION FUNCTIONS (Continued) 



Function 

fseekd'nt fp, int offset 
[ , i nt whence] ) 

mkdi r(stri ng pathname, 
int mode) 

rmdir(string dirname) 

readf i 1 e(stri ng filename 
[, int use_i ncl ude_path] ; 

umask( [i nt mask] ) 



f passthru( i nt fp) 

rename( stri ng old_name, 
string new_name) 

unl i nk( stri ng filename) 

ftruncate (int fp, int 
size) 

fstatd'nt fp) 



copy(string source_f i 1 e , 
string desti nati on_f i 1 e) 

freadd'nt fp, int length) 

fgetcsv(int fp, 
int length) 

real path(stri ng path) 



Returns Action 

int Seeks the position of a file pointer 



int 


Creates a directory 


int 


Removes a directory 


int 


Outputs a file or a URL 


int 


Returns or changes the umask. See 




the Unix man page on umask for 




further details 



int Outputs all remaining data from a 

file pointer 

int Renames a file 



int Deletes a file, similar to the C unlink 

function 

int Truncates file to the size indicated in 

the second argument 

int Returns the same information as 

stat() (described eariler) on a file 
handle 

int Copies a file 

int Conducts a binary- safe file read 

array Gets line from file pointer and parses 

for CSV fields 

string Returns the resolved path, from root. 

Works on symbolic links and 
references using .. or . 



Appendix E: PHP Function Reference 



455 



Table E- 9 PRINT FUNCTIONS 


Function 


Returns 


Action 


spri ntf (stri ng format 


string 


Returns a formatted string 


[, mixed argl 






[ , mixed ...]]) 







pri ntf (stri ng format 
[, mixed argl 
[ , mixed ...]]) 

pri nt_r(mixed var) 



var_dump(mixed var; 



int Outputs a formatted string 



void Prints out information about the 

specified variable 

void Dumps a string representation of the 

variable to output 



Table E-10 HTTP HEADER FUNCTIONS 



Function 

header(string header) 

setcookie(string name 
[, string value [, int 
expires [, string path 
[, string domain 
[, string secure]]]]]) 

headers_sent( void) 



Returns 


Action 


void 


Sends a raw HTTP header 


void 


Sends a cookie 



int 



Returns true if headers have already 
been sent; returns false otherwise 



456 



Part V: Appendixes 



Table E- 11 HTML FUNCTIONS 



Function Returns 

htmlspecialchars(string string 

string) 



htmlentities(string string) string 

get_html_transl ati on_ array 

table([int whichone]) 



Action 

Converts special characters 
(ampersand, double quotes, single 
quotes, less than, and greater than) 
to HTML entities 

Converts all applicable characters to 
HTML entities 

Returns the internal translation table 

used by html special chars and 
html enti ti es 





Table E- 12 MAIL FUNCTION 


Function 

mai 1 (string to , stri ng 
subject, string message 
[, string additional_ 
headers] ) 


Returns 

int 


Action 

Sends an e-mail message 




Table E- 13 RANDOM NUMBER FUNCTIONS 



Function 

srandd'nt seed) 

rand([int min, int max]' 

getrandmax( void) 
random number can have 

mt_s rand (int seed) 
number generator 



Returns Action 

void Seeds random number generator 

int Returns a random number 

int Returns the maximum value a 

void Seeds M ersenne Tw ister random 



Appendix E: PHP Function Reference 



457 



Function 

mt_rand( [int min, int max]! 

mt_getrandmax( void) 



Returns Action 

int Returns a random number from 

Mersenne Twister 

int Returns the maximum value a 

random number from Mersenne 
Twister can have 



Table E-14 REGULAR EXPRESSION FUNCTIONS 



Function 

ereg(string pattern, 
string string [, array 
registers]) 

eregi(string pattern, 
string string [, array 
registers]) 

ereg_replace( string 
pattern, string 
replacement, string 
string) 

eregi_replace( string 
pattern, string 
replacement, string 
string]) 

spl i t( stri ng pattern , 
string string [, int 
limit]) 

spl iti (string pattern, 
string string [, int 
limit]) 

sql_regcase(stri ng 
string) 



Returns Action 

int Conducts a regular expression match 



int Case- insensitive regular expression 

match 



string Replaces regular expression 



string Conducts a case- insensitive replace 

regular expression 



array Splits string into array by regular 

expression 

array Splits string into array by a case- 

insensitive regular expression 



string M akes regular expression for case- 

insensitive match 



458 



Part V: Appendixes 



Table E- 15 STRING MANIPULATION FUNCTIONS 



Function 


Returns 


Action 


bin2hex(string data) 


string 


Converts the binary representati 
a number to hex 


strspn(string str, 


int 


Finds length of initial segment 


string mask) 




consisting entirely of characters 
found in mask 


strcspn(string str, 


int 


Finds length of initial segment 


string mask) 




consisting entirely of characters 
not found in mask 


rtrimt string str) 


string 


Alias for chopf) 


chop(string str) 


string 


Strips trailing white space 


trim(string str) 


string 


Strips white space from the 
beginning and end of a string 


ltrim(string str) 


string 


Strips white space from the 
beginning of a string 


wordwrap(stri ng str 


string 


Wraps buffer to selected numbei 



[ , int width [, strinc 
break] ] ) 



expl ode( stri ng separator, array 

string str [ , int limit]) 

impl ode( array src, string 

stri ng gl ue) 

join(array src, string 

string glue) 

strtok( [string str, string 

] string token) 

strtoupper( stri ng str) string 

strtol ower( stri ng str) string 

basenamefstring path) string 



characters using the specified width. 
The line is broken with the character 
in the third argument or \n. If no 
width is given, the string will be 
broken at 75 characters 

Splits a string on string separator 
and returns array of components 

J oins array elements by placing glue 
string between items and returns 
one string 

Alias for imploded 

Tokenizesa string 

Makes a string uppercase 

Makes a string lowercase 

Returns the filename component of 
the path 



Appendix E: PHP Function Reference 



459 



Function Returns 

di rname(stri ng path) string 

strstr(stri ng haystack, string 

string needle) 

strchr(stri ng haystack, string 

string needle) 

stristrtstring haystack, string 

string needle) 

strpos(stri ng haystack, int 

string needle 
[, int offset]) 

strrpos(string haystack, int 

string needle) 

strrchr(stri ng haystack, string 

string needle) 

chunk_spl i t(stri ng str string 

[, int chunklen 

[, string ending]]) 

substr(string str, string 

int start [, int length]) 



substr_repl ace( stri ng str, string 
string repl, int start 
[, int length]) 

quotemeta( stri ng str) string 



ord(string character) int 

chr( i nt asci i ) string 



Action 

Returns the directory name 
component of the path 

Finds first occurrence of a string 
within another 

Alias for strstr( ) 



Finds first occurrence of a string 
within another (case- insensitive) 

Finds position of first occurrence of a 
string within another 

Finds position of last occurrence of a 
character in a string within another 

Finds the postion of the last 
occurrence of a character in a string 
within another 

Splits a line by inserting, by default, 
\r\n every 76 chracters. The length of 
the chunks and the separation string 
can be indicated 

Returns part of a string, as specified 
by the start position and length. If 
the length is a negative number, 
position is determined from the end 
of the string 

Replaces part of a string with 
another string 

Returns a string with the following 
characters prepended by a backslash: 

.\+*?n($) 

Returns ASCII value of character 
Converts ASCII code to a character 
Continued 



460 



Part V: Appendixes 



Table E-15 STRING MANIPULATION FUNCTIONS (Continued) 



Function 


Returns 


Action 


ucfirst(string str) 


string 


M akes a string's first character 
uppercase 


ucwords( stri ng str) 


string 


M akes the first character of every 
word in a string uppercase 


strtr(string str, string 


string 


Translates characters in str using 


from, string to) 




given translation tables 


strrev(string str) 


string 


Reverses a string 


simi 1 ar_text(stri ng strl, 


int 


Returns the number of charcters that 


string s t r 2 [, double 




are the same in the two strings. By 


percent] ) 




using a referenced varaible in the 
third argument, the precentage of 
similar characters is passed to the 
third argument 


addcsl ashes( stri ng str, 


string 


Escapes all chars mentioned in 


string charlist) 




charl ist with backslashes 


addsl ashes( stri ng str) 


string 


Escapes single quotes, double quotes, 
and backslash characters in a string 
with backslashes 


stripcslashes(string str) 


string 


Strips backslashes from a string. Uses 
C- style conventions 


stripslashesfstring str) 


string 


Strips backslashes from a string 


str_repl ace( stri ng needle, 


string 


Replaces all occurrences of needle in 


string str, string 




haystack with str 


haystack) 






hebrev(string str 


string 


Converts logical Hebrew text to 


[, int max_chars_per_l ine] ) 




visual text 


hebrevc(string str 


string 


Converts logical Hebrew text to 


[, int max_chars_per_l ine] ) 




visual text with newline conversion 


n!2br(string str) 


string 


Inserts HTM L line breaks after each 
newline 


strip_tags(string str 


string 


Strips HTM L and PHP tags from a 


[, string al 1 owable_tags] ) 




string 


setlocale(string category, 


string 


Sets locale information 



string locale) 



Appendix E: PHP Function Reference 



461 



Function 

parse_str( string 
encodecLstri ng) 

str_repeat( stri ng input, 
i n t mult) 

count_chars(stri ng input 
[ , i nt mode] ) 



strnatcmp( stri ng si, 
string s 2 ) 

strnatcasecmp( stri ng si, 
string s 2 ) 

substr_count(string 
haystack, string needle; 

str_pad(stri ng input, 
i nt pad_length 
[, string pad_string 
[, i nt pad_type]]) 

sscanf(string str, 
string format 
[ , string . . . ] ) 



Returns 


Action 


void 


Parses GET/POST/COOKIE data and 




sets global variables 


string 


Returns the input string repeated 




mult times 


mixed 


Returns info about what character 



are used in input string. If mode is 0, 
an associative array is returned with 
the byte value as key and the number 
of ocurrences as value. 

int Returns the result of string 

comparison using "natural" algorithm 

int Returns the result of a case- 

insensitive string comparison using 
"natural" algorithm 

int Returns the number of times a 

substring occurs in the string 

string Returns input string padded on the 

left or right to specified length with 

pad_stri ng 

mixed Implements an ANSI C-compatible 

sscanf 



Table E- 16 URL FUNCTIONS 



Function 

parse_url (string url ) 



Returns Action 

array Parses a URL and returns its 

components in an associative array. 
The array elements are: scheme (e.g., 
http), host (e.g., www.mydomai n . 
com), path (e.g., /index.php), query, 
which is the entire querystring. 



Continued 



462 Part V: Appendixes 



Table E-16 URL FUNCTIONS (Continued) 



Function 

urlencode(string str) 
urldecode(string str) 
rawurl encode(stri ng str) 

rawurl decode(stri ng str) 



Returns Action 

string URL- encodes string 

string Decodes URL- encoded string 

string URL-encodes string. See Chapter 6 

for the difference between this and 
url encoded. 

string Decodes URL- encoded string 



Table E- 17 VARIABLE SERIALIZING FUNCTIONS 



Function Returns 



seri al i ze(mixed variable) string 



unserialize(string mixed 

van' a b 1 e_representati on ) 



Action 

Returns a string representation 
of variable (which can later be 
unserialized) 

Takes a string representation of 
variable and recreates it 



Table E- 18 MISCELLANEOUS FUNCTIONS 



Function Returns 

ip21ong(string ip_address) int 

long2ip(int proper_address) string 



getenv( stri ng varname) 



putenv(stri ng setting) 



string 



void 



Action 

Converts a string containing an 
(IPv4) Internet Protocol dotted 
address into a proper address 

Converts an (IPv4) Internet network 
address into a string in Internet 
standard dotted format 

Gets the value of an environment 
variable 

Sets the value of an environment 
variable by using a format of 
putenv("ENV_VAR=$foo"); 



Appendix E: PHP Function Reference 



463 



Function 


Returns 


f 1 u s h ( v o i d ) 


void 


sleep(int seconds) 


void 


usleepCint micro_seconds) 


void 


g e t_c u r r e n t_u s e r ( v o i d ) 


string 


get_cfg_var(stri ng 


string 


opti on_name) 




i s_resource(mixed var) 


bool 


error log(string message, 


int 



int message_type 

[, string destination] 
[, string extra_headers] ) 

cal l_user_f unc( stri ng mixed 

f uncti on_name [, mixed 
parmeter] [ , mixed . . . ] ) 

cal l_user_method(stri ng mixed 

method_name, object 
object [, mixed 
parameter] [, mixed ...]) 



regi ster_shutdown„ void 

function(string 

f uncti on_name) 

high! i ght_fi 1 e( stri ng void 

f i 1 e_name) 

hi ghl i ght_stri ngtstri ng void 

string) 

i ni_get(stri ng varname) string 

i ni_set(stri ng varname, string 

string newvalue) 



Action 

Flushes the output buffer 

Delays for a given number of seconds 

Delays for a given number of 
microseconds 

Gets the name of the owner of the 
current PHP script 

Gets the value of a PHP 
configuration option 

Returns true if variable is a resource 

Sends an error message to an error 
log, TCP port, or file 



Calls a user function that is the first 

parameter 

Calls a user method on a specific 
object where the first argument is 
the method name, the second 
argument is the object, and the 
subsequent arguments are the 
parameters 

Registers a user- level function to be 

called on request termination 

Outputs a PHP source file with 
syntax highlights 

Syntax highlights a string 



Gets a configuration option 

Sets a configuration option, returns 
false on error and the string of the 
old value of the configuration option 
on success 



Continued 



464 



Part V: Appendixes 



Table E- 18 MISCELLANEOUS FUNCTIONS (Continued) 



Function Returns 

ini_restore(string varname) string 



connecti on_aborted( void) 


int 


connecti on_timeout( void) 


int 


connect i o n_s t a t u s ( v o id) 


int 


bitfield 




i gnone_user_abort (bool ean 


int 


val ue) 




getservbyname( string 


int 


service, string protocol) 




getservbyport( int port, 


string 


stri ng protocol ) 




getprotobyname(string name) 


int 


getprotobynumberd'nt proto) 


string 


get_l oaded_extens ions (void) 


array 


extensi on_l oaded( string 


bool 


extensi on_name) 




get_extensi on_f uncs( string 


array 



extensi on_name) 



Action 

Restores the value of a configuration 
option specified by varname to 

its original value set in 
the p h p . i n i 

Returns true if client disconnected 

Returns true if script timed out 

Returns the connection status 



Sets whether you want to ignore a 
user abort event or not 

Returns port associated with service 
(protocol must be tcp or udp) 

Returns service name associated with 
port (protocol must be "tcp" or "udp") 

Returns protocol number associated 
with name as per /etc/protocols 

Returns protocol name associated 
with protocol number proto 

Returns an array containing names 
of loaded extensions 

Returns true if the named extension 
is loaded 

Returns an array with the names of 
functions belonging to the named 
extension 



Table E-19 ARRAY FUNCTIONS 



Function 

krsort(array array_arg 
[, int sort_flags]) 



Returns Action 

int Sorts an array reverse by key 



Appendix E: PHP Function Reference 



465 



Function 

ksort(array array_arg 
[, int sort_flags]) 

natsort(array array_arg) 



Returns Action 

int Sorts an array by key 



natcasesort(array 
array_arg) 

asort(array array_arg 
[, int sort_flags]) 

arsort(array array_arg 
[, int sort_flags]) 

sort(array array_arg 
[, int sort_flags]) 

rsort(array array_arg 
[, int sort_flags]) 

usort(array array_arg, 
string cmp_f uncti on ) 

uasort(array array_arg, 
string cmp_f uncti on ) 

uksort(array array_arg, 
string cmp_f uncti on ) 

array_wal k( array input, 

string funcname 

[, mixed userdata]) 

count(mixed var) 



end(array array_arg) 



void 



void 



void 



void 



void 



void 



void 



void 



void 



int 



int 



mixed 



Sorts an array using natural sort. The 
difference between a natural sort 
and a normal sort is described here: 

http : //www. linuxcare.com. 
au/projects/natsort/ 

Sorts an array using case- insensitive 
natural sort 

Sorts an array and maintains index 
association 

Sorts an array in reverse order and 
maintains index association 

Sorts an array 

Sorts an array in reverse order 

Sorts an array by values using a user- 
defined comparison function 

Sorts an array with a user- defined 
comparison function and maintains 
index association 

Sorts an array by keys using a user- 
defined comparison function 

Applies a user function to every 
member of an array 

Counts the number of elements in a 
variable (usually an array) 

Advances array argument's internal 
pointer to the last element and 
returns it 



Continued 



466 



Part V: Appendixes 



Table E-19 ARRAY FUNCTIONS (Continued) 



Function Returns 

prev(array array_arg) mixed 

next(array array_arg) mixed 

reset(array array_arg) mixed 

current( array array_arg) mixed 

key(array array_arg) mixed 



min(mixed argl [, mixed mixed 

arg2 [ , mixed ...]]) 

max(mixed argl [, mixed mixed 

arg2 [ , mixed ...]]) 

i n_array (mixed needle, bool 

array haystack 
[, bool strict]) 

extract( array var_array, void 

int extract_type 
[, string prefix]) 

compact(mixed var_names array 

[ , mixed . . . ] ) 

range (int low, int high) array 



shuffl e( array array_arg) int 



Action 

M oves array argument's internal 
pointer to the previous element and 
returns it 

M oves array argument's internal 
pointer to the next element and 
returns it 

Sets array argument's internal 
pointer to the first element and 
returns it 

Returns the element currently 
pointed to by the internal array 
pointer 

Returns the key of the element 
currently pointed to by the internal 
array pointer 

Returns the lowest value in an array 
or a series of arguments 

Returns the highest value in an array 
or a series of arguments 

Checks if the given value exists in 
the array 

Imports variables into symbol table 
from an array 

Creates an array containing 
variables and their values 

Creates an array containing the 
range of integers from low to high 
(inclusive) 

Randomly shuffles the contents of an 
array. The random number generator 
must first be seeded with srandf) 



Appendix E: PHP Function Reference 



467 



Function 

array_push( array stack, 
mixed var [ , mixed . . . ] ] 

array_pop(array stack) 



array_shi ft(array stack) 

array_unshi ft(array 
stack, mixed var 
[ , mixed . . . ] ) 

array_spl i ce(array 
input, int offset 
[, int length [, array 
repl acement]] ) 

array_sl ice(array input, 
int offset [, int length]) 

array_merge(array arrl, 
array arr2 [ , array . . . ] ) 

array_merge_recursi ve 
(array arrl, array arr2 
[ , array . . . ] ) 

array_keys( array input 
[, mixed search_val ue] ) 

array_val ues(array input) 

array_count_val ues(array 
input) 

array_reverse(array input) 

array_pad(array input, 
int pad_size, mixed 
pad_val ue) 

array_fl i p( array input) 



Returns 


Action 


int 


Pushes elements onto the end of the 




array 


mixed 


Pops an element off the end of the 




array 


mixed 


Pops an element off the beginning of 




the array 


int 


Pushes elements onto the beginning 




of the array 



array Removes the elements designated by 

offset and l ength and replaces 
them with supplied array 

array Returns elements specified by 

offset and length 

array M erges elements from passed arrays 

into one array 

array Recursively merges elements from 

passed arrays into one array. 

array Returns just the keys from the input 

array, optionally only for the 
specified search_value 

array Returns just the values from the 

input array 

array Returns the value as key and the 

frequency of that value in input as 
value 

array Returns a new array with the order 

of the entries reversed 

array Returns a new array padded with 

pad_val ue to size pad_si ze 

array Returns array with key <-> value 

flipped 



Continued 



468 



Part V: Appendixes 



Table E-19 ARRAY FUNCTIONS (Continued) 



Function Returns 

array_unique(array input) array 

array_intersect(array arrl, array 
array arr2 [ , array . . . ] ) 



array_di ff (array arrl, array 

array arr2 [ , array . . . ] ) 

array_mul ti sort( array arl bool 

[, SORT_ASC|SORT_DESC 
[, SORT_REGULAR|SORT_ 
NUMERIC | SORT_STRING]] 
[, array ar2 [, S0RT_ASC 
S0RT_DESC [, SORT_REGULAR[ 
SORT_NUMERIC|SORT_ 
STRING]], ...]) 

array_rand(array input mixed 

[, int num_req]) 



Action 

Removes duplicate values from array 

Returns the entries of arrl that 
have values that are present in all 
the other arguments 

Returns the entries of arrl that 
have values that are not present in 
any of the other arguments 

Sorts multiple arrays at once (works 
like the ORDER BY clause in SQL). 
Retruns TRUE on success, FALSE on 
failure. 



If the second argument is blank or 
set to 0, this will return a single key 
form the input array. If the second 
argument is greater than 0, it will 
return an array, each element of 
which is a random key from the input 
array. 



Table E-20 MYSQL FUNCTIONS 



Function 

my sql_connect( [string 
hostname[: port] [: /path/to/ 
socket]] [, string 
username] [, string 
password] ) 



Returns Action 

int Opens a connection to a MySQL 

Server. Returns FALSE on failure 



Appendix E: PHP Function Reference 469 



Function 

mysql_pconnect([string 
hostname [: port] [ : /path/ 
to/socket]] [, string 
username] [, string 
password] ) 

mysql_cl ose( [i nt link_ 
identifier]) 

mysql_selec t_d b ( s t r i n g 
database_name [, i nt 

1 i nk_i denti f i er] ) 

mysql_creat e_d b ( s t r i n g 
database_name [, i nt 
1 i nk_i denti fi er] ) 

mysql_drop_db(string 
database_name [, i nt 
1 i nk_i denti fi er] ) 

mysql_query (stri ng query 
[, i nt ] i nk_i denti fier] ) 

my s q 1 _d b_q uery(string 
database_name , string 
query [, i nt link_ 
identifier]) 

my s q 1 _1 i s t_d b s ( [ i n t 
1 i nk_i denti fi er] ) 

mysql_l ist_tables(string 
database_name [, i nt 
1 i nk_i denti fi er] ) 

my s q 1 _1 is t_f i e 1 d s ( s t r i n g 
database_name , string 
tab]e_name [, int link_ 
identifier]) 

mysql_error( [i nt link_ 
identi fier]) 



Returns Action 



int 



int 



int 



int 



int 



int 



int 



int 



int 



int 



Opens a persistent connection to a 
M ySQL Server 



Closes a MySQL connection. Does not 
effect persistent connections 

Selects a MySQL database 



Creates a MySQL database 



Drops (deletes) a MySQL database 



Sends an SQL query to M ySQL 



Sends an SQL query to M ySQL 



Lists databases available on a MySQL 
server 

Lists tables in a M ySQL database 



Lists MySQL result fields 



string Returns the text of the error message 

from the previous MySQL operation 



Continued 



470 



Part V: Appendixes 



Table E-20 MYSQL FUNCTIONS (Continued) 



Function Returns 

mysql_errno( [i nt link_ int 

identifier]) 

mysql_affected_rows( [i nt int 

1 i nk_identi f ier] ) 

mysql_insert_id( [int link_ int 

identifier] ) 

mysql_resul t( i nt result, int 

int row [, mixed field]) 

mysql_num_rows(int result) int 

mysql_num_fi elds (int result) int 

mysql_fetch_row(int result) array 

mysql_fetch_object(int object 

result [, int resul t_type]) 

mysql_fetch_array(int array 

result [, int resul t_type]) 

mysql_data_seek( i nt result, int 
int row_number) 

mysql_fetch_l engths( i nt array 

resul t ) 

mysql_fetch_field(int object 

result [, int field_offset] ) 

mysql_field_seek(int result, int 

int field_offset) 



mysql_f i el d_name( i nt string 

result, int field_index) 

mysql_field_table(int string 

result, int f i el d_off set ) 



Action 

Returns the number of the error 
message from previous MySQL 
operation 

Gets number of affected rows in 
previous MySQL operation 

Gets the number generated from the 
previous INSERT operation, where 
there is an autojncrement column 

Gets result data 

Gets number of rows in a result 

Gets number of fields in a result 

Gets a result row as an enumerated 
array 

Fetches a result row as an object 

Fetches a result row as an associative 
array, a numeric array, or both. 

Moves internal result pointer. Creates 
an error if given an invalid row 

Gets max data size of each column in 
a result 

Gets column information from a 
result and returns as an object 

Sets result pointer to a specific 
field offset. The next call to 
mysqlfetchfieldO will use this 
offset 

Gets the name of the specified field 
in a result 

Gets name of the table the specified 
field is in 



Appendix E: PHP Function Reference 



471 



Function 


Returns 


my s q 1 _f i e 1 d_l e n ( i n t 


int 


result, int f iel d_offet) 




mysql_f i el d_type( i nt 


string 


result, int field_offset) 




mysql_field_flags(int 


string 


result, int field_offset) 




mysql free resultCint 


int 



resul t ) 



Action 

Returns the length of the specified 
field 

Gets the type of the specified field in 
a result 

Gets the flags associated with the 
specified field in a result 

Frees result memory 



Table E-21 ASPELL FUNCTIONS 



Function 

aspel l_new( stri ng master 
[ , string personal ] ) 

aspel l_suggest( aspel 1 
int, string word) 

aspel l_check(aspel 1 
int, string word) 

aspel 1 _check_raw( aspel 1 
int, string word) 



Returns 


Action 


int 


Loads a dictionary 


array 


Returns array of suggestions 


bool 


Returns TRUE if a word is valid, 




FALSE if it is not 


int 


Returns TRUE if word is valid 



To use the aspell functions, you need the aspell library from http:// 

metal ab. unc.edu/kevina/aspel 1 /. That PHP is configured with -- 
with-aspell 




472 



Part V: Appendixes 



Table E-22 BCMATH FUNCTIONS 



Function 

bcsub(string 1 eft_operand , 
string ri ght_operand 
[, int scale]) 

bcmul (stri ng 1 eft_operand , 
string ri ght_operand 
[, int scale]) 

bcdiv(string 1 eft_operand , 
string ri ght_operand 
[, int scale]) 



bcmod(stri ng 1 eft_operand , 


string 


string ri ght_operand) 




bcpow (string x, string y 


string 


[, int scale]) 




bcsqrt( stri ng operand 


string 


[, int scale]) 




bccomp(string left_operand, 


string 


string right_operand [, int 




scale] ) 





bcscale(int scale) 



Returns Action 

string Returns the difference between two 

arbitrary- precision numbers 

string Returns the multiplication of two 

arbitrary- precision numbers 

string Returns the quotient of two 

arbitrary- precision numbers (division) 



Returns the modulus of the two 
arbitrary- precision operands 

Returns the value of an arbitrary- 
precision number raised to the power 
of another 

Returns the square root of an 
arbitrary- precision number 

Compares two arbitrary- precision 
numbers 



string Sets default scale parameter for all 

be math functions 











Use of the bemath functions requires PHP to be complied with -enable- 
bc-math. 










Table E- 23 CALENDAR FUNCTIONS 


Function Returns Action 

jdtounixCint jday) int Converts Julian Day to Unix 

timestamp 



Appendix E: PHP Function Reference 



473 



Function 

jdtogregorianUnt 
jul i andaycount) 

gregori antojd( i nt month, 
int day, i nt year) 

jdtojul i an( i nt 
jul i andaycount) 

jul i antojd( i nt month , 

int day, int year) 

jdtojewi sh( i nt 
jul i andaycount) 

jewi shtojd( i nt month, 
int day, int year) 

jdtofrench(int 
jul i andaycount) 

f renchtojd( i nt month, 

cal endar 

int day, int year) 

jddayofweek( i nt 
jul i andaycount 
[ , int mode] ) 

jdmonthname 

(int jul i andaycount , 

int mode) 



Returns Action 

string Converts a Julian Day count to a 

Gregorian calendar date 

int Converts a Gregorian calendar date 

to Julian Day count 

string Converts a Julian Day count to a 

Julian calendar date 



int Converts a Julian calendar date to 

Julian day count 

string Converts a Julian Day count to a 

Jewish calendar date 

int Converts a J ewish calendar date to a 

Julian Day count 

string Converts a Julian Day count to a 

French Republic calendar date 

int Converts a French Republic 

date to Julian Day count 

mixed Returns name or number of day of 

week from J ulian Day count 



string Returns name of month for J ulian 

Day count 




UseofthecalendarfunctionsrequiresPHPto complied with-enable-calendar. 



474 



Part V: Appendixes 



Table E- 24 COM FUNCTIONS 



Function 

com_load( string module_name) 

com_i nvoke( i nt module, 
string h a nd 1 e rename 
[ , mixed arg [ , ...]]) 

com_propget( i nt module, 
string property__name) 

com_propput( i nt module, 
string property_name, 
mixed value) 



Returns Action 

int Loads a COM module 

mixed Invokes a COM module 

mixed Gets properties from a COM module 

bool Puts the properties for a module 




These will work when PHP is installed with NSorPWS. 



Table E-25 CYBERCASH FUNCTIONS 



Function Returns 

cybercash_encr (string wmk, array 
string sk, string inbuff) 



cybercash_decr (string wmk, array 
string sk, string inbuff) 



cybercash_base64_encode string 

(string inbuff) 

cybercash_base64_decode string 

(string inbuff) 



Action 

Returns an associative array with the 
elements errcode and, if errcode is 
false, outbuff (string), outLth (long) 
and macbuff (string) 

Returns an associative array with the 
elements errcode and, if errcode is 
false, outbuff (string), outLth (long) 
and macbuff (string) 

Encodes a string in a way that 
Cybercash will accept 

Decodes a string received from 
Cybercash 



Appendix E: PHP Function Reference 



475 



Use of the cybercash functions requires the Cybercash libraries and that PHP 
be configured -with-cybercash.You can see an example of how to use these 
functions with the following class: http://www.zend.com/codex.php?id= 
115&single=l. 




Table E- 26 DBASE FUNCTIONS 



Function 

d b 1 i s t ( v o i d ) 

dbmopen(stri ng filename, 
string mode) 

dbmcl osednt dbm_ 
identifier) 

dbmi nsert( i nt dbm_ 
identifier, string key, 
string value) 

dbmreplaceCint dbm_ 
identifier, string key, 
string value) 

dbmfetchd'nt dbm_ 
identifier, string key) 

dbmexi sts( i nt dbm_ 
identifier, string key) 

dbmdel ete( i nt dbm_ 
identifier, string key) 

dbmf i rstkey ( i nt dbm_ 
identifier) 

dbmnextkey ( i nt dbm_ 
identifier, string key) 



Returns Action 

string Describes the dbm- compatible library 

being used 

int Opens a dbm database 



bool Closes a dbm database 



int Inserts a value for a key in a dbm 

database 



int Replaces the value for a key in a dbm 

database 



string Fetches a value for a key from a dbm 

database 

int Tells if a value exists for a key in a 

dbm database 

int Deletes the value for a key from a 

dbm database 

string Retrieves the first key from a dbm 

database 

string Retrieves the next key from a dbm 

database 



476 



Part V: Appendixes 




PHP must be compiled --with-dbasein order to use these functions. 



Table E- 27 DBA FUNCTIONS 



Function 

dba_popen(stri ng path, 
string mode, string 
handlername [, string ...]) 

dba_open(stri ng path, 
string mode, string 
handlername [, string ...]) 

dba_cl ose( i nt handle) 

dba_exi sts ( stri ng key, 
i nt handle) 

dba_fetch(stri ng key, int 
handl e) 

dba_f i rstkeyC i nt handle) 



dba_nextkey ( i nt handle) 

dba_del ete( stri ng key, 
int handle) 

dba_i nsert( stri ng key, 
string value, int handle) 

dba_repl ace( stri ng key, 
string value, int handle) 

dba_optimi ze( i nt handle) 



dba_sync(int handle) 



Returns Action 

int Opens path using the specified 

handler in mode persistently 



int Opens path using the specified 

handler in mode 



void 


Closes database 


bool 


Determines whether the specified key 
exists 


string 


Fetches the data associated with key 


string 


Resets the internal key pointer and 
returns the first key 


string 


Returns the next key 


bool 


Deletes the entry associated with 
the key 


bool 


Inserts value as key, returns false if 
key already exists 


bool 


Inserts value as key, replaces key if 
key already exists already 


bool 


Optimizes (e.g. cleans up, vacuums) 
database 


bool 


Synchronizes database 



Appendix E: PHP Function Reference 



477 




PHP must be compiled with --enable-dbain order to use these functions. 



Table E- 28 FTP FUNCTIONS 



Function 

ftp_l ogind'nt stream, 
string username, string 
password) 

ftp_pwd(int stream) 



Returns 


Action 


int 


Logs into the FTP server 


string 


Returns the present working 




directory 



ftp_cdup(int stream) 


int 


ftp_chdi r( i nt stream, 


int 


string directory) 




ftp_mkdi r( i nt stream, 


string 


string directory) 




ftp_rmdi r( i nt stream, 


int 


string directory) 




ftp_nl i stCint stream, 


array 


string directory) 




ftp_rawl i st( i nt stream, 


array 


string directory) 




ftp_systype( i nt stream) 


string 


ftp_fget(int stream, 


int 


int fp, string remote_ 




file, int mode) 





ftp_pasv(int stream, 
int p a s v ) 



int 



Changes to the parent directory 
Changes directories 

Creates a directory 

Removes a directory 

Returns an array of filenames in the 
given directory 

Returns a detailed listing of a 
directory as an array of output lines 

Returns the system type identifier 

Retrieves a file from the FTP server 
and writes it to an open file 

Turns passive mode on or off 



Continued 



478 



Part V: Appendixes 



Table E-28 FTP FUNCTIONS (Continued) 



Function Returns 

ftp_get(int stream, int 

string local_file, 
string remote_file, 
int mode) 

ftp_fput(int stream, int 

string local _file, string 
remote_file, int mode) 

ftp_put(int stream, int 

string remote_file, 
string local_file, 
int mode) 

ftp_size(int stream, int 

string path) 

ftp_mdtm(int stream, int 

string path) 

ftp_rename( i nt stream, int 

string src, string dest) 



Action 

Retrieves a file from the FTP server 
and writes it to a local file 



Stores a file from an open file to the 
FTP server 



Stores a file on the FTP server 



Returns the size of the file, in bytes 
or -1 on error 

Returns the last modification time of 
the file or -1 on error 

Renames the given file to a new path 



ftp_del eted'nt stream, 


int 


Deletes a file 


string path) 






ftp_site(int stream, 


int 


Sends a SITE 


string cmd) 







ftp_quit(int stream) 



int 



Closes the FTP stream 




PHP must be compiled -with-ftp in order to have access to these functions. 



The gd functions can make and manipulate images on the fly and can work with 
several types of image formats: jpeg, gif, png, and WBMP (used for protable 
devices). Note that Unisys holds the patent to the type of compression used in gif 
images. When they started enforcing the patent, libraries such as GD had to drop 



Appendix E: PHP Function Reference 



479 



their support of gif images. There, if you want to use the GD functions with gif 
images, you will need a version of the GD libraries older than 1.6. However, these 
older libriaries do not support png files. Versions later than 1.6 do support png. 



Table E- 29 GD FUNCTIONS 



Function 

imagecreate( i nt x_size, 
int y_size) 

imagetypes( voi d) 



Returns Action 

int Creates a new image 



int 



imagecreatef romgi f 


int 


(string filename) 




imagecreatef romj peg 


int 


(string filename) 




imagecreatef rompng 


int 


(string filename) 




imagecreatef romwbmp 


int 


(string filename) 




imagegif (int im 


int 


[, string filename]) 




imagepngd'nt im 


int 


[, string filename]) 





imagejpeg( i nt im int 

[, string filename 
[ , int qual i ty]] ) 

imagewbmp( i nt im int 

[, string filename]) 

imagedestroy ( i nt im) int 

imagecolorallocate(int int 
im, int red, int green, 
int blue) 



Returns the types of images 
supported in a bitfield— l=gif, 
2=jpeg,4=png, 8=wbmp 

Creates a new image from GIF file 
or URL 

Creates a new image from J PEG file 
or URL 

Creates a new image from PNG file 
or URL 

Creates a new image from WBM P file 
or URL 

Outputs GIF image to browser or file 



Outputs PNG image to browser or file 



Outputs J PEG image to browser 
or file 



Outputs WBM P image to browser 
or file 

Destroys an image 

Allocates a color for an image, will 
usually be assigned to a variable for 
later use. 



Continued 



480 



Part V: Appendixes 



Table E- 29 GD FUNCTIONS (Continued) 



Function Returns 

imagepal ettecopy ( i nt int 
dst, int src) 
bl ue) 

imagecol orcl osesthwb( i nt int 
im, int red, int green, 
i nt bl ue) 

imagecol ordeal 1 ocate( i nt int 
m, int index) 

magecol orresol ve( i nt im, int 
nt red, int green, 
nt bl ue) 

magecol orexact( i nt im, int 
nt red, int green, 
nt bl ue) 

magecol orset( i nt im, int 
nt col, int red, 
nt green, int blue) 

imagecol orsfori ndex array 
(int i m , int col) 

i magegammacorrect int 

(int i m , double 
inputgamma, double 
outputgamma ) 

imagesetpixel ( i nt im, int 
int x , int y , int col) 

imagel i ne( i nt im, int xl, int 
int yl, int x2, int y2, 
int col) 

imagedashedl i ne( i nt im, int 
int xl, int yl, int x2, 
int y 2 , int col) 

imagerectangl e( i nt im, int int 
xl, int yl, int x2, int y 2 , 
int col) 



Action 

Copies the palette from the src 
image onto the dst imzthe 
pallete to the specified color 

Gets the index of the color with the 
hue, whiteness and blackness nearest 
to the given color 

De-allocates a color for an image 

Gets the index of the specified color 
or its closest possible alternative 

Gets the index of the specified color. 
Returns -1 if the color does not exist 



Sets the color for the specified 
palette index 

Gets the colors for an index, in red, 
green, and blue 

Applies a gamma correction to a GD 
image 



Sets a single pixel; the x and y 
coordinates start at the top left, 
and col is the color for the pixel 

Draws a line 



Draws a dashed line 



Draws a rectangle 



Function 

imagefilledrectangle 
( i n t i m , i n t x 1 , i n t 
yl, int x2, int y2, 
i n t col) 

imagearcCint im, int ex, 

int cy, int w, int h, 
int s , int e , int col) 

imagefilltoborderUnt 
i m , int x, i n t y , int 
border , i nt col ) 

imagefi 1 1 (int im, int x, 

int y , int col) 

imagecol orstotal (int im) 

imagecolortransparent 
(int i m [ , int col]) 

imagei nterl ace( i nt im 
[, int interlace]) 

imagepolygont i nt im, 
array point, int 
nurrupoi nts , i nt col ) 

imagefilledpolygon(int 
im, array point, int 
num_poi nts , i nt col ) 



Appendix E: PHP Function Reference 481 

Returns Action 

int Draws a filled rectangle 



int 



int 



int 



int 



int 



int 



int 



int 



Draws a partial ellipse 



Fills the image im, to the borders 
specified by x and y coordinates with 
the color in the fifth argument 

Floods fill starting at the x and y 
coordinates using the color specified 
in the fourth argument 

Returns the number of colors in an 
image's palette 

Defines a color as transparent. 
Returns the identifier of the new 
color 

Enables or disables interlace 



Draws a polygon. The array will take 
the following form: points[0] = xO, 
points[l] =y0, points[2] =xl, 
points[3] =yl 

Draws a filled polygon 



imagefontwidth(i nt font) 


int 


Gets font width 


imagefontheightO'nt font) 


int 


Gets font height 


imagechar( i nt im, int 


int 


Draws a character 


font, int x, int y, 






string c, int col) 







Continued 



482 



Part V: Appendixes 



Table E- 29 GD FUNCTIONS (Continued) 



Function Returns 

imagecharup( i nt im, int 

int font, int x, int y, 
string c, int col) 

imagestri ng( i nt im, int int 

font, int x, int y, string 
s t r , int col) 

imagestri ngup( i nt im, int int 

font, int x, int y, string 
s t r , int col) 

imagecopy ( i nt dst_im, int 

int src_im, int dst_x, 
int dst_y, int src_x, 
int src_y, int src_w, 
int src_h) 

imagecopymerge( i nt src_im, int 

int dst_im, int dst_x, 
int dst_y, int src_x, 
int src_y, int src_w, 
int src_h, int pet) 

imagecopyresi zed( i nt int 

dst_im, int src_im, 

int dst_x, int dst_y, 

int src_x, int src_y, 

int dst_w, int dst_h, 

int src_w, int src_h) 

imagesx(int im) int 

imagesy(int im) int 

imagettfbboxO'nt size, array 

int angle, string font_file, 
string text) 

imagettftext( i nt im, int array 

size, int angle, int x, 
int y, int col, string 
font_file, string text) 

imagepsl oadfont int 

(string pathname) 



Action 

Draws a character rotated 90 degrees 
counterclockwise 



Draws a string horizontally 



Draws a string vertically— rotated 
90 degrees counterclockwise 

Copies part of an image 



M erges one part of an image with 
another 



Copies and resizes part of an image 



Gets image width 

Gets image height 

Gives the bounding box of a text 
using TrueType fonts 

Writes text to the image using a 
TrueType font 



Loads a new font from specified file 



Appendix E: PHP Function Reference 483 



Function 

imagepsfreefont 
(int font_index) 

imagepsextendfont 
(int font_index, 
double extend) 

imagepstext( i nt image, 
string text, int font, 
int size, int xcoord, 
int ycoord [, int space, 
int tightness, double 
angle, int ant i alias]) 

imagepsloadfont( string 
pathname) 

imagepsfreefontO'nt 
f ont_i ndex) 

imagepsencodefontUnt 
font_index, string 
f i 1 ename) 

imagepsextendfontUnt 
font_index, double 
extend) 

imagepssl antfontd'nt 
font_index, double slant) 

imagepstext( i nt image, 
string text, int font, 
int size, int xcoord, 
int ycoord [, int space, 
int tightness, double 
angle, int ant i alias]) 

imagepsbbox(stri ng text, 
int font, int size 
[, int space, int 
tightness, int angle]) 

imagepsbbox(stri ng text, 
int font, int size 
[, int space, int 
tightness, int angle]) 



Returns Action 

bool Frees memory used by a font 



bool 



array 



int 



bool 



bool 



bool 



bool 



array 



array 



array 



Extends or condenses (if extend < 1) 
a font 

Draws a text string over an image 



Loads a new font from specified file 

Frees memory used by a font 

Changes a font's character encoding 
vector 

Extends or condenses (if extend < 1) 
a font 

Slants a font 

Rasterizes a string over an image 



Returns the bounding box needed by 
a string if rasterized 



Returns the bounding box needed by 
a string if rasterized 



484 



Part V: Appendixes 




Useofthese functions requires the GD library from http: //www. 
boutel 1 .com/gd/ and for PHP to be compiled --with-gd. 



Table E- 30 IMAP FUNCTIONS 



Function 

imap_open(stri ng mailbox, 
string user, string 
password [, int options]) 

imap_popen( stri ng mailbox, 
string user, string 
password [, int options]) 

imap_reopen( i nt stream_id, 
string mail box [ , int 
options]) 

imap_cl ose( i nt stream_id 
[, int options]) 

imap_append( i nt stream_id, 
string folder, string 
message [, string flags]) 

imap_num_msg(i nt stream_id) 

imap_pi ng( i nt stream_id) 

imap_num_recent( i nt 
stream_i d) 

imap_expunge(int strearrMd) 



imap_headers(int stream_id) 

imap_body( int stream_id, 
int msg_no [, int options]) 



Returns Action 

int Opens an IMAP stream to a mailbox 



int Opens a persistant IMAP stream to a 

mailbox 

int Reopens an IMAP stream to a new 

mailbox 



int Closes an IMAP stream 

int Appends a new message to a 

specified mailbox 



int Gives the number of messages in the 

current mailbox 

int Checks if the IMAP stream is still 

active 

int Gives the number of recent messages 

in current mailbox 

int Permanently deletes all messages 

marked for deletion 

array Returns headers for all messages in a 

mailbox 

string Reads the message body 



Appendix E: PHP Function Reference 485 



Function 

imap_fetchtext_f ul Hint 
stream_id, int msg_no 
[, int options]) 

imap_mai l_copy ( i nt 
stream_id, int msgjo, 
string mailbox 
[, int options]) 

imap_mai l_move( i nt 
stream_id, int msg_no, 
string mailbox 
[, int options]) 

imap_createmai 1 box 
(int stream_id, 
string mailbox) 

imap_renamemailbox 
(int stream_id, string 
old_name, string 
new_name) 

imap_del etemai 1 box 
(int stream_id, string 
mai 1 box) 

imap_l i st( i nt stream_id, 
string ref, string 
pattern) 

i map_getmai 1 boxes ( i nt 
stream_id, string ref, 
string pattern) 

imap_check(int stream_id) 

imap_del ete( i nt 
stream_id, int msg_no 
[, int flags]) 

imap_undel ete( i nt 
stream_id, int msg_no) 



Returns Action 

string Reads the full text of a message 



int Copies specified message to a 

mailbox 



int M oves specified message to a 

mailbox 



int 



int 



int 



array 



Creates a new mailbox 



Renames a mailbox 



Deletes a mailbox 



Reads the list of mailboxes 



array Reads the list of mailboxes and 

returns a full array of objects 
containing name, attributes, and 
delimiter 

object Gets mailbox properties 

int Marks a message for deletion 



int 



Removes the delete flag from a 
message 

Continued 



486 



Part V: Appendixes 



Table E-30 IMAP FUNCTIONS (Continued) 



Function Returns 

imap_headeri nfo( i nt object 

stream_id, int msg_no 

[, int from_length 

[, int subject_l ength 

[, string d e f a u 1 1_ 

host]]]) 

imap_rfc822_parse_headers 
(string headers [, string 
defau] t_host] ) 
imap_headeri nf o( ) 

imap_l sub( int stream_id, 
string ref, string pattern) 

imap_getsubscri bed( i nt 
stream_id, string ref, 
string pattern) 

imap_subscri be( i nt 
stream_id, string mailbox) 

imap_unsubscri be( i nt 
stream_id, string mailbox) 

imap_fetchstructure( i nt object 

stream_id, int msg_no 
[, int options]) 

imap_fetchbody(int string 

stream_id, int msg_no, 
int section [ , int 
options]) 

imap_base64( stri ng text) string 

imap_qpri nt( stri ng text) string 

imap_8bit(string text) string 

imap_bi nary ( stri ng text) string 



Action 

Reads the headers of the message 



object 


Parses a set of mail headers 




contained in a string and returns an 




object, much like 


array 


Returns a list of subscribed 




mailboxes 


array 


Returns a list of subscribed 




mailboxes, in the same format as 




i map_getmai 1 boxes ( ) 


int 


Subscribes to a mailbox 


int 


Unsubscribes from a mailbox 



Reads the full structure of a message 



Gets a specific body section. The 
different portions of the IMAP body 
are defined in the IMAP RFC 

Decodes BASE64- encoded text 

Converts a quoted- printable string to 
an eight- bit string 

Converts an eight- bit string to a 
quoted- printable string 

Converts an eight- bit string to a 
base64 string 



Appendix E: PHP Function Reference 



487 



Function 

imap_mai 1 boxmsgi nfo( i nt 
stream_id) 

i map_rf c822_wri te_ 
address (stri ng mailbox, 
string host, string 
personal ) 

imap_rf c822_parse_ 
adrl i st(stri ng address_ 
string, string default_ 
host) 

imap_utf8(stri ng string) 

imap_utf7_decode(stri ng 
buf) 

i ma p_utf7_en code (stri ng 
buf) 

imap_setf 1 ag_f ul 1 ( i nt 
stream_id, string 
sequence, string flag 
[, i nt options]) 

imap_cl earf 1 ag_f ul 1 
(int stream_id, string 
sequence, string flag 
[, int options]) 

imap_sort( i nt stream_id, 
int criteria, int 
reverse [, int options]) 

imap_fetchheader( i nt 
stream_id, int msg_.no 
[, int options]) 

imap_uid(int stream_id, 
int msg_no) 

imap_msgno( i nt stream_id, 
int unique_msg_id) 



Returns Action 

object Returns info about the current 

mailbox 

string Returns a properly formatted e- mail 

address given the mailbox, host, and 
personal info 

array Parses an address string 



string Converts a string to UTF-8 

string Decodes a modified UTF-7 string 

string Encodes a string in modified UTF-7 

int Sets flags on messages 



int Clears flags on messages 



array Sorts an array of message headers 



string Gets the full, unf iltered header for a 

message 

int Gets the unique message ID 

associated with a standard 
sequential message number 

int Gets the sequence number 

associated with a UID 



Continued 



488 



Part V: Appendixes 



Table E-30 IMAP FUNCTIONS (Continued) 



Function 

imap_status(int stream_id, 
string mail box, int 
options) 

imap_bodystruct( i nt 
stream_id, int msg_no, 
int section) 

imap_fetch_overview( i nt 
stream_id, int msg_no) 

imap_mai l_compose(array 
envelope, array body) 

imap_mai 1 (stri ng to , 

string subject, string 

message [, string 

addi ti onal_headers 

[, string cc [, string bcc 

[, string rpath]]]]) 

imap_search( i nt stream_id, 
string criteria [, long 
flags]) 



imap_al erts( void) 



Returns 

object 

object 
array 

string 

int 



array 



array 



imap_errors(void) 



array 



imap_l ast_error( void) 



string 



Action 

Gets status info from a mailbox 



Reads the structure of a specified 
body section of a specific message 

Reads an overview of the 
information in the headers of 
the given message sequence 

Creates a M IM E message based on 
given envelope and body sections 

Sends an e-mail message 



Returns a list of messages matching 
the given criteria. The criteria are 
listed on the manual page: http:// 
www.php.net/manual /function, 
imap-search.php 

Returns an array of all IMAP alerts 
generated since the last page load or 
since the last imap_alerts( ) call, 
whichever came last; the alert stack 
is cleared after imap_alerts( ) is 
called 

Returns an array of all IMAP errors 
generated since the last page load, or 
since the last imap_errors( ) call, 
whichever came last; the error stack is 
cleared after imap_errors( ) is called 

Returns the last error generated by 
an IMAP function; the error stack is 
NOT cleared after this call 



Appendix E: PHP Function Reference 



489 



Function 

i ma p_m i m e_h e a d e r_d e c o d e 
(string str) 



Returns Action 

array Decodes MIMEheader element in 

accordance with RFC 2047 and 
returns array of objects containing 
charset encoding and decoded text 



Use of the IMAP functions requires the IMAP libraries and PHP to be 
installed --with-imap.The functions will work with a POP3 server as well. 




Table E-31 INTERBASE FUNCTIONS 



Function Returns 

i base_connect( stri ng int 

database [, string 

username] [, string 

password] [, string 

charset] [, int buffers] 

[ , int dialect] 

[, string role]) 

i base_pconnect( stri ng int 

database [, string 

username] [, string 

password] [, string 

charset] [, int buffers] 

[, int dialect] 

[, string role]) 

i base_cl ose( [i nt int 

1 i nk_i dentif ier] ) 

i base_commi t( [i nt int 

1 i nk_i denti f i er , ] 
int trans_number) 



Action 

Opens a connection to an InterBase 
database and returns a connection 

identifier 



Opens a persistent connection to an 
InterBase database 



Closes an InterBase connection 



Commits transaction 



Continued 



490 



Part V: Appendixes 



Table E- 31 INTERBASE FUNCTIONS (Continued) 


Function Returns 


Action 


i base_rol 1 back( [i nt link_ int 


Rolls back transaction 


identifier,] int trans_ 




number) 





i base_query ( [i nt link_ int 

identifier,] string query 
[, int bind_args]) 

ibase_prepare( [i nt link_ int 

identifier,] string query) 

ibase_fetch_row( i nt result 
[, int blob_flag]) 

i base_fetch_object( i nt 
result [, int blob_flag]) 

i base_f ree_resul t( i nt result) 

i base_execute( i nt query 
[, int bind_args 
[, int ...]) 

ibase_free_query(int query) int 

i base_timefmt(stri ng int 

format) 



i base„num_f i el ds ( i nt 




int 


resul t ) 






i base_f i el d_i nf o( i nt 




array 


result, int field_numb 


31") 




ibase_bl ob_add(int blob_ 


.id, 


int 


string data) 







Executes a query 



Prepares a query for later execution 



array 


Fetches a row from the results of a 




query 


object 


Fetches an object from the results of 




a query 


int 


Frees the memory used by a result 


int 


Executes a previously prepared query 



Frees memory used by a query 

Sets the format of timestamp, date, 
and time columns returned from 
queries 

Gets the number of fields in result 



Gets information about a field 



Adds data into created blob 




PHP must be installed -with-ibase in order for these functions to work. 
Please check the current documentation on PHP interbase functions, 
because as of the time of this writing, this API was under a lot of flux. 



Appendix E: PHP Function Reference 



491 



Table E- 32 M HASH FUNCTIONS 



Function 

mhash_count( ) 

mhash_get_bl ock_si ze 
( i n t hash) 

mhash_get_hash_name( i nt 
hash ) 

mhash(int hash, string 
data ) 



Returns 


Action 


int 


Gets the number of available hashes 


int 


Gets the block size of hash 


string 


Gets the name of hash 


string 


Computes hash function on data, 



using the hash in the first argument. 



Useofthese functions requires themhash library from http: //mhash . 
sou reef orge.net /.PHP must be compiled -with-mhash. 




The msql database is another open-source SQL database server. It is not actively 
maintained and has some limitiations that make it a poor choice when compared to 
MySQL, PostGRES, or Interbase. Note that msqp and MySQL have almost identical 
function sets. 



Table E- 33 MSQL FUNCTIONS 



Function 

msql_connect([string 
hostname[ : port]] 
[, string username] 
[, string password]) 

msq]_pconnect([strinc 
hostname[ : port]] 
[, string username] 
[, string password]) 



Returns Action 

int Opens a connection to an mSQL 

Server 



int 



Opens a persistent connection to an 
mSQL Server 



Continued 



492 



Part V: Appendixes 



Table E-33 MSQL FUNCTIONS (Continued) 


Function Returns 

msql_cl ose( [i nt link_ int 
identifier]) 


Action 

Closes an mSQL connection 



msql_sel ect_db( stri ng int 

database_name [, int 
1 i nk_identi f ier] ) 

msql_create_db( stri ng int 

database_name [, int 
1 i nk_identi f ier] ) 

msql_drop_db(stri ng int 

database_name [, int 
] i nk_identi f ier] ) 

msql_query ( stri ng query int 

[, int 1 i nk_identi f i er] ) 

msql_] i st_dbs ( [i nt link_ int 

identi f i er] ) 

msql_] i st_tab] es(stri ng int 

database_name [, int 
] i nk_identi f ier] ) 

msql_] i st_f i e] ds(stri ng int 

database_name , string 
table_name [, int link_ 
identifier]) 

msql_error( [i nt link_ string 

identifier]) 

msql_resu] t( i nt query, int 

int row [, mixed field]) 

msql_num_rows ( i nt query) int 

msql_num_f i el ds( i nt query) int 

msql_fetch_row( i nt query) array 

msql_fetch_object( i nt object 

query [, int resul t_type] ) 

msql_fetch_array ( i nt query array 
[, int resul t_type] ) 



Selects an mSQL database 



Creates an mSQL database 



Drops (deletes) an mSQL database 



Sends an SQL query to mSQL 

Lists databases available on an mSQL 
server 

Lists tables in an mSQL database 



Lists mSQL result fields 



Returns the text of the error message 
from previous mSQL operation 

Gets result data 



Gets number of rows in a result 

Gets number of fields in a result 

Gets a result row as an enumerated 
array 

Fetches a result row as an object 

Fetches a result row as an associative 
array 



Appendix E: PHP Function Reference 



493 



Function 

msql_data_seek( i nt query, 
int row_number) 

msql_fetch_f i eld( int 
query [, int fielcL 
offset]) 

msql_f i el d_seek(i nt 
query, int f iel d_off set) 

msql_f iel d_name( i nt 
query, int field_index) 

msql_f i el d_tabl e( i nt 
query, int f iel d_off set) 

msql_f iel d_l en( i nt query, 
int field_offet) 

msql_f i el d_type( i nt 
query, int f iel d_off set) 

msql_f i el d_f 1 ags( i nt 
query, int f iel d_offset) 

msql_free_result(int 
query) 

msql_affected_rows(int 
query) 



Returns Action 

int M oves internal result pointer 

object Gets column information from a 

result and returns as an object 

int Sets result pointer to a specific field 

offset 

string Gets the name of the specified field 

in a result 

string Gets name of the table the specified 

field is in 

int Returns the length of the specified 

field 

string Gets the type of the specified field in 

a result 

string Gets the flags associated with the 

specified field in a result 

int Frees result memory 

int Returns number of affected rows 



Table E-34 MSSQL FUNCTIONS 



Function 

mssql_connect( [string 
servername [, string 
username [, string 
password] ] ] ) 

mssql_pconnect([string 
servername [, string 
username [, string 
password] ] ] ) 



Returns Action 

int Establishes a connection to a M S- 

SQL server, returns a connection 
identifier 

int Establishes a persistent connection 

to a MS- SQL server 



Continued 



494 



Part V: Appendixes 



Table E-34 MSSQL FUNCTIONS (Continued) 



Function Returns 

mssql_cl ose( [i nt int 

connection id]) 

mssql_sel ect_db( stri ng bool 

database_name [, int 
conn_id] ) 

mssql_query ( stri ng query 
[, int conn_id]) 

mssql_free_result( string 
resul t_i ndex) 

mssql_get_l ast_message 
( voi d) 

mssql_num_rows ( i nt mssql_ 
resul t_i ndex) 

mssql_num_f i el ds( i nt 
mssql_resul t_i ndex) 

mssql_fetch_row( i nt 
resul t_i d) 

mssql_fetch_object( i nt object 

resul t_i d) 



mssql_fetch_array ( i nt array 

resul t_i d) 

mssql_data_seek( i nt int 

resul t_i d , int offset) 



mssql_fetch_f i el d( i nt object 

resul t_i d [, int offset]) 

mssql_f i el d_l ength( i nt int 

resul t_i d [, int offset]) 



Action 

Closes a connection to a M S- SQL 
server 

Selects a MS- SQL database 



int 


Performs an SQL query on a M S- SQL 




server database 


int 


Frees a MS- SQL result index 


string 


Gets the last message from the 




MS- SQL server 


int 


Returns the number of rows fetched 




in from the result ID specified 


int 


Returns the number of fields fetched 




in from the result ID specified 


array 


Returns an array of the current 



row in the result set specified by 

resul t_id 

Returns an object of the current 
row in the result set specified by 

resul t_id 

Returns an associative array of the 
current row in the result set specified 

by resul t_id 

M oves the internal row pointer of 
the MS- SQL result associated with 
the specified result identifier to 
pointer to the specified row number 

Gets information about a certain 
field in a query result 

Gets the length of a MS- SQL field 



Appendix E: PHP Function Reference 



495 



Function 

mssql_f i el d_name( i nt 
result_id [, int offset] '. 



mssql_f i el cLtypet i nt 
result_id [, int offset]! 



Returns 

string 

string 



mssql_field_seek(int 


bool 


result_id, int offset) 




mssql_resul t( i nt 


string 


resulted, int row, 




mixed field) 




mssql min error severityfint 


void 


severity) 




mssql _mi n_message_ 


void 


severity (int severity) 





Action 

Returns the name of the field given 
by offset in the result set given by 
resultjd 

Returns the type of afield 



M oves pointer to the specified field 
offset 

Returns the contents of one cell from 
a MS- SQL result set 

Sets the lower error severity 

Sets the lower message severity 



Table E-35 PERL COMPATIBLE REGULAR EXPRESSION FUNCTIONS 



Function Returns 

preg_match( stri ng int 

pattern, string subject 
[, array subpatterns] ) 

preg_match_al 1 ( stri ng int 

pattern, string subject, 
array subpatterns 
[, int order]) 

preg_repl ace(string | string 

array regex, string|array 
replace, string|array 
subject [, int limit]) 

preg_spl i t( stri ng array 

pattern, string subject 
[ , int limit [ , int 
flags]]) 



Action 

Performs a Perl- style regular 

expression match 

Performs a Perl- style global regular 



expression match 

Performs a Perl- style regular 
expression replacement 



Splits string into an array using a 
perl- style regular expression as a 
delimiter 



Continued 



496 



Part V: Appendixes 



Table E-35 PERL COMPATIBLE REGULAR EXPRESSION FUNCTIONS (Continued) 



Function 

preg_quote( stri ng str, 
string del im_char) 

preg_grep(stri ng regex, 
array input) 



Returns Action 

string Quotes regular expression characters 

plus an optional character 

array Searches array and returns entries 

that match regex 



Table E- 36 POSTGRES FUNCTIONS 



Function Returns 

pg_connect( [stri ng int 

connecti on_stri ng] | 

[string host, string port 

[, string options 

[, string tty,]] string 

database) 

pg_pconnect( [stri ng int 

connecti on_stri ng] | 

[string host, string port 

[, string options 

[, string tty,]] string 

database) 

pg_cl ose( [i nt connection]) bool 

pg_dbname( [int connection]) string 

pg_errormessage( [i nt string 

connection]) 

pg_options( [int connection]) string 

pg_port([int connection]) int 

pg_tty([int connection]) string 

pg_host([int connection]) string 



Action 

Opens a PostgreSQL connection 



Opens a persistent PostgreSQL 
connection 



Closes a PostgreSQL connection 
Gets the database name 
Gets the error message string 

Gets the options associated with the 
connection 

Returns the port number associated 
with the connection 

Returns the tty name associated with 
the connection 

Returns the host name associated 
with the connection 



Appendix E: PHP Function Reference 



497 



Function 

pg_exec([int connection,] 
string query) 

pg_numrows( i nt result) 



pg_numf i el ds ( i nt result) 

pg_cmdtupl es ( i nt result) 

pg_f i el dname( i nt result, 
i nt f i el d_number) 

pg_f i el dsi ze( i nt result, 
i nt f i el d_number) 

pg_f i el dtype( i nt result, 
i nt f i el d_number) 

pg_f i el dnum( i nt result, 
string field_name) 

pg_resul t( i nt result, 
i nt row_number, mixed 
f i el d_name) 

pg_fetch_row( i nt result, 
i nt row) 

pg_fetch_array ( i nt 
result, int row [, i nt 
resul t_type] ) 

pg_fetch_object( i nt 
result, int row [, int 
resul t_type] ) 

pg_f i eldprtlen(int 
result, int row, mixed 
f i el d_name_or_number) 

pg_f i el di snul 1 ( i nt 
result, int row, mixed 
f iel d_name_or_n umber) 



Returns Action 

int Executes a query 

int Returns the number of rows in the 

result 

int Returns the number of fields in the 

result 

int Returns the number of affected 

tuples 

string Returns the name of the field 

int Returns the internal size of the field 

string Returns the type name for the given 

field 

int Returns the field number of the 

named field 

mixed Returns values from a result 

identifier 

array Gets a row as an enumerated array 

array Fetches a row as an array 



object Fetches a row as an object 



int 



int 



Returns the printed length 



Tests if a field is NULL 



Continued 



498 



Part V: Appendixes 



Table E-36 POSTGRES FUNCTIONS (Continued) 



Function Returns 

pg_f reeresul t(int result) int 

pg_getl astoidd'nt result) int 

pg_trace(stri rg filename bool 

[, string mode [, resource 
connecti on] ] ) 

pg_untrace( [i nt bool 

connection]) 

pg_l ocreateO'nt connection) int 

pg_l ounl i nk( [i nt void 

connection,] int large_ 
obj_id) 

pg_loopen( [int connection,] int 

int objoid, string mode) 



pg_l ocl ose( i nt fd) 


void 


pg_l oread( int fd, int len) 


string 


pg_l owri te( i nt f d , 


int 


string buf) 




pg_l oreadalld'nt fd) 


void 


pg_l oimport( stri ng 


int 


filename [, resource 




connecti on] ) 




pg_l oexport( i nt objoid, 


bool 


string filename 




[, resource connection]) 




pg_setcl ientencoding 


int 


([int connection,] 




string encoding) 




pg_cl i entencodi ng 


string 


([int connection]) 





Action 

Frees result memory 

Returns the last object identifier 

Enables tracing of a PostgreSQL 
connection 

Disables tracing of a PostgreSQL 
connection 

Creates a large object 

Deletes a large object 



Opens a large object and returns fd 

Closes a large object 
Reads a large object 
Writes a large object 

Reads a large object and sends 
straight to browser 

Imports large object direct from 
filesystem 

Exports large object directly to 
filesystem 

Sets client encoding 



Gets the current client encoding 



Appendix E: PHP Function Reference 



499 





Useofthese functions 


requires that PI- 


HP be compiled --with-postgres. 









Table E-37 SESSION FUNCTIONS 


Function 


Returns 


Action 


sessi on_set_cooki e_ 


void 


Sets session cookie parameters 


params( i nt 1 i fetime 






[, string path [, string 






domai n]] ) 






sessi on_get_cooki e_ 


array 


Returns the session cookie 


params(void) 




parameters 


sessi on_ 


name( [stri ng 


string 


Returns the current session name; if 



newname] ) 

sessi on_modul e_name string 

([string newname]) 



sessi on_set_save_handl er void 

(string open, string 
close, string read, 
string write, string 
destroy, string gc) 

sessi on_save_path( [string string 

newname] ) 



session_id( [string newid]) string 



sessi on_regi ster(mixed bool 

var_names [, mixed ...]) 



newname is given, the session name 
is replaced with newname 

Returns the current module name 
used for accessing session data; if 
newname is given, the module name 
is replaced with newname 

Sets user- level functions 



Returns the current save path passed 
to modul e_name; if newname is 
given, the save path is replaced with 

newname 

Returns the current session id; if 
newi d is given, the session id is 
replaced with newid 

Adds variable names to the list of 
variables stored by the session 

Continued 



500 



Part V: Appendixes 



Table E-37 SESSION FUNCTIONS (Continued) 



Function 

sessi on_unregi ster 
(string varname) 

sessi on_i s_regi stered 
(string varname) 

sessi on_encode( void) 



sessi on_decode( stri ng data) 



sessi on_start( void) 



sessi on_destroy ( void) 



sessi on_unset( void) 



Returns 


Action 


bool 


Removes varname from the list of 




variables stored by the session 


bool 


Checks if a variable is registered in 




session 


string 


Serializes the current setup and 




returns the serialized representation 


bool 


Deserializes data and reinitializes the 




variables 


bool 


Begins session — reinitializes freezed 



variables, registers browsers, and so 
forth 

bool Destroys the current session and all 

data associated with it 

void Unsets all registered variables 




Session functions are described in more detail in Chapter 14. 



Table E- 38 DNS FUNCTIONS 



Function 


Returns 


gethost by addr( string 


string 


i p_address ) 




gethost by name ( string 


string 


hostname) 




gethostbynamel (string 


array 


hostname) 





Action 

Gets the Internet host name 
corresponding to a given IP address 

Gets the IP address corresponding to 
a given Internet host name 

Returns a list of IP addresses that a 
given host name resolves to 



Appendix E: PHP Function Reference 501 



Function 

checkdnsrr( stri ng host 
[, string type]) 

getmxrr(stri ng hostname, 
array mxhosts [, array 
weight] ) 



Returns Action 

int Checks DNS records corresponding to 

a given Internet host name or IP 
address 

int Gets M X records corresponding to a 

given Internet host name 



Table E- 39 MATH FUNCTIONS 

Function 

absd'nt number) 

ceil (doubl e number) 

floor(double number) 

round(double number 
[, int precision]) 

sin(double number) 
cos(double number) 
tan(double number) 
asin(double number) 
acos(double number) 
atan(double number) 



Returns 


Action 


int 


Returns the absolute value of the 




number 


int 


Returns the next highest integer 




value of the number 


int 


Returns the next lowest integer value 




of the number 


double 


Returns the number rounded to 




specified precision 


double 


Returns the sine of the number in 




radians 


double 


Returns the cosine of the number in 




radians 


double 


Returns the tangent of the number in 




radians 


double 


Returns the arc sine of the number in 




radians 


double 


Return the arc cosine of the number 




in radians 


double 


Returns the arc tangent of the 



number in radians 



Continued 



502 Part V: Appendixes 



Table E-39 MATH FUNCTIONS (Continued) 



Function 


Returns 


atan2(doubl e y, double x) 


double 


pi (void) 


double 


pow(double base, double 


double 


exponent) 




exp(double number) 


double 


log(double number) 


double 



loglCKdouble number) double 

sqrt(double number) double 

deg2rad(doubl e number) double 

rad2deg(doubl e number) double 

bi ndec( stri ng bi nary_number ) int 



hexdec( stri ng 
hexadecimal _n umber) 



int 



octdectstring octal_number) int 



decbindnt decimal_number) string 



decoctdnt decimal_number) string 



dechexdnt decimal_number) string 



Action 

Returns the arc tangent of y/x, with 
the resulting quadrant determined by 
the signs of y and x 

Returns an approximation of pi 

Returns base raised to the power of 
exponent 

Returns e raised to the power of the 
number 

Returns the natural logarithm of the 
number 

Returns the base- 10 logarithm of the 
number 

Returns the square root of the number 

Converts the number in degrees to 
the radian equivalent 

Converts the radian number to the 
equivalent number in degrees 

Returns the decimal equivalent of 
the binary number 

Returns the decimal equivalent of 
the hexadecimal number 

Returns the decimal equivalent of an 
octal string 

Returns a string containing a binary 
representation of the number 

Returns a string containing an octal 
representation of the given number 

Returns a string containing a 
hexadecimal representation of the 
given number 



Appendix E: PHP Function Reference 



503 



Function Returns 

base_convert(stri ng number, string 
int frombase, int tobase) 

number_format(double string 

number [, int num_decimal_ 
places [, string dec_ 
seperator, string 
thousands_seperator]] ) 



Action 

Converts a number in a string from 
any base <= 36 to any base <= 36. 

Formats a number with grouped 
thousands 





Table E- 40 MD5 


Function 


Returns 


Action 


md5(string str) 


string 


Calculates the md5 hash of a string 


Table E-41 OUTPUT BUFFERING 


Function 


Returns 


Action 


ob_start( void) 


void 


Turns on output buffering 


ob_end_f 1 ush( void) 


void 


Flushes (sends) the output buffer and 
turns off output buffering 


ob_end_cl ean( void) 


void 


Cleans (erases) the output buffer and 
turns off output buffering 


ob_get_contents(void) 


string 


Returns the contents of the output 
buffer 


ob_impl i ci t_f 1 ush( [i nt 
flag]) 


void 


Turns implicit flush on/off and is 
equivalent to calling fl ush( ) after 
every output call 



504 



Part V: Appendixes 



Table E-42 PAYFLOW PRO FUNCTIONS 


Function 


Returns 


Action 


pfpro_versi on( ) 


string 


Returns the version of the Payflow 
Pro library 


pfpro_i ni t( ) 


void 


Initializes the Payflow Pro library 


pfpro_cl eanup( ) 


void 


Shuts down the Payflow Pro library 


pfpro_process_raw(stri ng 


string 


Performs a raw Payflow Pro 


parml i st [ , stri ng 




transaction 


hostaddress [, int port, 






[, int timeout [, string 






proxyAddress 






[, int proxyPort 






[, string proxyLogon 






[ , string 






proxy Password]]]]]]]) 






pfpro_process (array 


array 


Performs a Payflow Pro transaction 


parml i st [ , stri ng 




using arrays 


hostaddress [, int port, 






[, int timeout [, string 






proxyAddress [, int 






proxyPort [, string 






proxyLogon [, string 






proxy Password]]]]]]]) 






g^O/ftf 


Use of these functio 


ns requires payflo 


pro libraries from verisign and PHP to 


ME 


compile with --with 


payflo. 




Table E- 43 CURL FUNCTIONS 



Function 

curl _vers ion (void) 
curl _i nit ([string url]' 



Returns Action 

string Returns the CURL version string 

int Initializes a CURL session 



Appendix E: PHP Function Reference 



505 



Function 

curl_setopt (int ch, 
string option, mixed 
val ue) 

curl_exec (int ch) 

curl _c lose (int ch) 



Returns Action 

bool Sets an option for a CURL transfer 

bool Performs a CURL session 

void Closes a CURL session 



Useofthese functions requires the curl library and PHPto be compiled -- 
with-curl. The cURL functions arediscussed in moredetail in Chapter 14. 




Appendix F 



Regular Expressions 
Overview 



Regular expressions provide a means for pattern matching in strings. Patterns 
may be as simple as a literal string or a literal string with a wildcard character, or 
they can grow to be very complex. How complex? Check out the following exam- 
ple, which is intended for e-mail validation. If you're new to regular expressions 
this may look bad, but to tell the truth, it's not nearly nasty enough. In fact to 
properly validate an e-mail, it takes about 200 lines of regular expressions. See 
Appendix G for an e-mail validation function that's quite a bit more complete. 

A [_\.0-9a-z-]+@([0-9a-z][0-9a-z-]+\.)+[a-z]{2,3}$ 

When you're working with PHP and MySQL there are three variants of regular 
expressions you might need to use, the regular PHP functions, the Perl-compatible 
regular expression functions and MySQL regular expression functions. The PHP 
ereg( ), eregi ( ), ereg_repl ace( ), and eregi_repl ace( ) functions use the pat- 
terns described here. 

The Perl Compatible Regular Expressions (PCRE) are quite different in places, 
and they offer some functionality that can't be replicated with the standard ereg( ) 
functions. After you have a good feel for regular expressions, you should probably 
head over to this page to view some of the differences for yourself: http://www. 

perl.com/pub/doc/manual/html/pod/perlre.html. The major PCRE functions 
are preg_match( ), preg_match_al 1 ( ), and preg_repl ace( ). 

Finally, there is another slight variant of the regular expressions used in MySQL, 
which is described in Appendix I of the MySQL manual. 



Literal Patterns 



The simplest possible pattern match is to a series of known characters. For instance, 
to match "jay" within a string, you could do this. 

$str = "this is a string with my name: jay"; 

if ( ereg("jay", $str)) 

{ 

echo "pattern found" ; 
) 507 



508 Part V: Appendixes 

el se 
{ 

echo "string not found"; 
) 

This will test true and print "pattern found". However, with a simple string like this, 
you wouldn't need a regular expression. One of PHP's string functions would work 
and be a good deal faster. For example, in the preceding example, strstr($str, 
"jay") would work equally well. 

Characters 

In regular expressions you can make use of the following characters. 

\n— Newline 

\t-Tab 

\r— Return 

\f — Form feed 

A (Shift-6) -Start of string 

$— End of string 

. (dot)— Matches any non-newline character. 

So if you needed to match the word "jay" at the end beginning of a string, you 
could do this: 

ereg( " A jay" , $str) 

And if you wanted to make sure there was nothing before or after "jay" in the 
string, you could do the following: 

ereg( " A jay$" , $str) 

Notice the meaning of the dot (.). It stands for any non-newline character. If you 
wanted to print whatever four characters followed "jay" in a string, you could do 
the following: 

ereg( "jay (....)" , $str, $arr); 
echo $arr[l]; 



Appendix F: Regular Expressions Overview 509 

Note that the parentheses here represent a substring. When eregt ) is processed 
and there is a match, the array in the third argument will contain the entire 
matched string (including substrings) in $arr[0], and each additional substring 
indicated by parentheses will be assigned to an additional array element. So in the 
preceding example, the four characters following "jay" will be in $arr[l]. 




The array created in the optional third argument of eregt ) will always con- 
tain 10 elements. The first element is the entire matched string. It can only 
place nine matched substrings in the other array elements. If there are fewer 
than 9 substrings indicated, those elements will be willed with empty strings. 



Character Classes 



Often you will need to see if a string contains a group of characters. For instance, 
you may need to make sure that a single character or given set of characters is 
alphanumeric or consists of a digit or digits. For this, you will make use of charac- 
ter classes. You can make use of the built-in character classes or make your own. 
The built-in character classes are surrounded by two sets of brackets. Character 
classes of your own making will be surrounded by a single set of brackets. 

Built-in character classes 

[[: alpha:]]— Any letter, upper or lower case 

[[:digit:]] -Digits (0-9) 

[[: space:]]— Matches any whitespace character, including spaces, tabs, 
newlines, returns, and form feeds 

[[: upper:]]— Matches only uppercase letters 

[[: lower: ]] —Matches only lowercase letters 

[[ :punct: ]] —Matches any punctuation mark 

[[:xdigit:]] —Matches possible hexadecimal characters 

For example, say you wanted to make sure a letter contained punctuation after 
"Dear Sir or Madam" salutation. 

ereg("Madam[[:punct:]]", $str); 



510 Part V: Appendixes 



Note that if you use the carat symbol ( A ) within a character class it has the effect 
of saying not. So, ereg( "Madam[ A [ :punct]]" , $str) would match only if Madam 
is not followed by a punctuation mark. 




The carat symbol can get confusing because it has two distinct meanings.At 
the beginning of a regular expression it indicates the start of a string. So the 
following regular expression will match only a string in which a digit is the 
first character: 

A [[:digit]] 

But if the carat is not in the first position in the regular expression, it means 
"not.'The following regular expression would match a string that does not 
contain any digits. 

[ A [:digit:]] 

And to put it all together, the following matches a string that starts with a 
digit but has a second character that is not a digit. 
A [[:digit:]][ A [:digit:]] 



Self- made character classes 

Using brackets, you can construct your own character classes either by using 
ranges of characters or by mixing characters of your choosing. Here are some typ- 
ical ranges: 

♦ a-z— Any lowercase letter 

♦ A-Z— Any uppercase letter 

♦ 0-9-Any digit 

Note that these are the ones you will see most frequently, but a range could con- 
tain a-m or 0-4 if you wished. 

These ranges must be put within brackets to become character classes. So 



[a-zA-Z] 

is identical to [[ : al pha : ]]. 

Self-made classes don't have to contain a range; they can contain any characters 
you wish. 

[dogO-9] 



Appendix F: Regular Expressions Overview 511 



This class will match the letters d, o, or g, or any digit. 

$str="drat" ; 

if(ereg(" A [dogO-9]", $str)) 
{ 

echo "true"; 
)el se{ 

echo "false"; 



This code will print "true", because the first character in $str is in the class I've 
defined. If we replaced the d in drat with a b, this code would print "false". 




If you need to include a hyphen within a class, the hyphen must be the final 
character before the closing bracket of the class. For example [a-zA-Z-] 



Multiple Occurrences 



The real fun in regular expressions comes when you deal with multiple occurrences. 
This is when the syntax starts getting a little thick. I'll start by looking at three 
commonly used special characters. 

♦ * (asterisk)— Zero or more of the previous character 

♦ +— One or more of the previous character 

♦ ? —Zero or one of the previous character 

Note that if you want to match any of these characters literally, you will need to 
escape it with a backslash. So, for example, if you want to match the querystring of a 

URL, say, http://www.mysqlphpapps. com/index. php?foo=mystri ng, you could 
do the following: 

\?.*$ 

The first two characters (\?) match the question mark character (?). Note that it 
matches the literal question mark because it is escaped with a backslash. If it were 
not escaped, the question mark would have the meaning given in the previous list- 
ing. Then the dot matches any a non-newline character. The asterisk matches zero 
or more of the pervious character. So the combination .* will match any number of 
characters until a newline. You will see the .* combination frequently. The dollar 



512 Part V: Appendixes 



sign is the end of string character. So .*$ matches every non newline character to 
the end of the string. 

You would probably want to use a regular expression like the previous one if 
you need to make use of the querystring in some other context 

$str="http://domain.com/index. php?foo=mystri ng&bar=otherstring" ; 

//see the use of the parenthesized substring 

//this will assign the matched portion to $array[l] 

if (ereg("\?( .*)$" , $str, $array) ) 

{ 

echo "The querystring is ", $array[l]; 

} 

Now that you have the querystring in the variable $array[l], you do further pro- 
cessing on it. 

Before you incorporate this code into your script, note that you don't have to. 
You could use the Apache variable $QUERY_STRING or the PHP HTTP_GET_VARS 
array. 

Moving on, since the plus sign means one or more of the previous character, 

[0-9]+ 

will match a single digit or multiple digits. In the following statement: 

if (ereg("jay[0-9]+" , $str) ) 

jayl will test true, butjayg will test false, jay2283092002909303 will test true because 
it's still "jay" followed by one or more numbers. Even, jay8393029jay will test true. 

If you need to get more specific about the number of characters you need to 
match, you can make use of curly braces. 

♦ (3) —If there is a single digit within brackets, it indicates that you wish 
to match exactly that number of the previous character, j {3 ) matches 
only jjj. 

♦ (3, 5) —If there are two digits, it indicates an upper and lower limit to 
the matches of the previous character. j( 3, 5) will match jjj, jjjj, and 
jjjjj only. 

♦ (3, } —If there is a comma and there is no second integer, it will match 
as many times or more of the previous character. So j (3, ) will 
match jjj, jjjj, or jjjjjjj, and so on. 



Appendix F: Regular Expressions Overview 513 



Specifying "Or" 



If you want to specify one combination of characters or another, you need to make 
use of the pipe character (|). Most often, you will use the pipe with parentheses, 
which group portions of strings. If you wanted to match either jay or brad within a 
string, you could use the following: 

(jay | brad) 

Or you might want to check that URLs had a suffix you were familiar with: 

(com | org | edu) 

Example Regular Expressions 

This has been a pretty quick review of regular expressions. If you're interested, 
there have been entire books written on the subject. To get you more comfortable 
with regular expressions, lets look at a practical example. 

Say you want to write a regular expression that matches the contents of an href 
attribute of an anchor tag. An example anchor looks something like this: 

<a href=" . . /my_l i rk. php">thi s is my link text</a> 

At first, you might tempted to look at this link and think all you need to do is 
match everything after the href-' to the closing quotation mark. Something like this: 

if (ereg('<a href=" (.*)"' , lanchor, $array)) 
t 

echo $array [1] ; 
} 

However, you really can't be sure that the href will immediately follow the <a; 
there could be another attribute or perhaps a javascript event prior to the href. So 
you'd need to account for that in your regular expression. 

if (ereg( ' <a .*href=" (.*)"' , lanchor, $array)) 
( 

echo $array [1] ; 



514 Part V: Appendixes 



We've seen anchor tags where a space existed prior to and following the equal 
sign. So we need to account for occasions when the space exists and when it doesn't. 

if (ereg('<a.*href[[: space: ]]?=[[: space :]]?"(.*)"' , 

lanchor, larray)) 

{ 

echo larray [1] ; 



Since the question mark character means "zero or one of the previous character", 
the pairing [[ :space: ]]? means there can be one whitespace character or none. If 
you wanted to allow for more than one whitespace character, you could use 

[[:space:]]+. 

Finally, we need to deal with the actual contents of the href attribute. So far, we've 
only accounted for cases where the link destination is delimited by double quotes. But 
at the very least, we should account for delimiters of either double quotes or single 
quotes. To do that, we'll need to put double quotes and single quotes within a char- 
acter class. Because we've surrounded the entire regular expression with single 
quotes, we will need to escape single quotes within the regular expression with back- 
slashes. The class will be ["\']. 

if (ereg( '<a.*href [[: space :]]?=[[: space: ]]?["\ '](.*)[ "\ ']' , 

lanchor, $array)) 
{ 

echo larray [1] ; 



To be even more complete, the regular expression should account for cases when 
no quotation mark at all is used to delimit value of the href. For example, browsers 
are just fine with a tag like this: <a href=myi ink.php>. In a case like this, it might 
be a good idea to use the greater than sign to mark the end of the string. All you 
would need to do is add the greater than sign to the last character class 

if (ereg ( '<a.*href [[: space :]]?=[[: space: ]]?["\ ']?(.*)[ "\ '>]' , 

lanchor, $array)) 
{ 

echo $array [1] ; 



However, this presents some problems that you may not have anticipated. 
Imagine that this previous code is attempting to match this string: <a href = ../ 
my_i ink.php>this is my link text</a>. When you add the greater than sign to 
the character class, the regular expression will not match the first greater than 
sign— it will match to the final greater than sign in the string. This is known as 



Appendix F: Regular Expressions Overview 515 

greedy matching, and using eregO or ereg_replace( ) there is no way around 
greedy matching. 

In a circumstance when you need to match the first occasion of a character in a 
string, you will need to make use of the PCRE functions. Using PCRE, the combination 
.*? will match all characters until the first occasion of the character you indicated. 
This series 

.*?["V>] 

will match everything until the first double quote, single quote or greater than sign. 
With preg_match( ) the final function would look like this: 

if 

(preg_match( ' /<a .*href[[ : space: ]]?=[[: space : ]]?["\ ']?(.*?) ["\ ' >]/i ' , 

$anchor, $array)) 
t 

echo $array [1] ; 



Appendix G 

Helpful User- Defined 
Functions 



This appendix contains a series of PHP functions and classes that you might find 
useful in creating your scripts. It will start with a run-through of the base functions 
kept in the /book/functions folder. 

Base Functions Set 
Used in this Book 

We discuss these in detail in Chapter 9, but we include them here for quick reference. 

from functions/basic.php 

These functions deal with authentication and text manipulation. 

AUTHENTICATEO 

This function sends a "401 Unauthorized" header. The default string is "Secure 
Area". 

void authenticate ([string realm], [string error_message] ) 

DB_ AUTHENTICATEO 

This function attempts to run 401-type authentication and verify the results 
against a given database table. The default table is mysql. users. It makes calls to 
authenticate( ) to send the 401 header. 

void db_authenti cate( [stri ng table [, string realm [, string error 
message [, string username field name [, string password field 
name]]]] ) 

CLEANUP_TEXT() 

This function removes HTML and PHP tags using the strip_tags( ) function and 
replaces <, >, &, and " characters with their HTML entities. If the second argument 
is not empty, striptags will not be run and only the HTML entity replacement will 
occur. The third argument can specify tags that should not be removed. 517 



518 Part V: Appendixes 



string cleanup_text ([string value [, string preserve [, string 
al 1 owed_tags]]] ) 

GET_ATTLIST() 

This function uses the PHP function htmlspecialchars( ) to convert special HTML 
characters in the first argument (&,",',<, and >) to their equivalent HTML entities. If 
the optional second argument is empty, any HTML tags in the first argument will be 
removed. The optional third argument lets you specify specific tags to be spared 
from this cleansing. The format for the argument is "<tagl><tag2>". 

string get_attlist (array attri butes , [array default attributes]) 

MAKE_PAGE_TITLE() 

This function will clean up a string to make it suitable for use as the value of an 
HTML <title> tag, removing any HTML tags and replacing all HTML entities with 
their literal character equivalents by using get_html_translation_table (HTML_ 
ENTITIES). 

string make_page_ti tl e (string string) 

MONEY() 

This function will format the sole argument as a standard U.S. dollars value, round- 
ing any decimal value two decimal places for cents and prepending a dollar sign to 
the returned string. Commas will server as thousands separators. 

string money ([mixed value]) 

STATESO 

This function returns an associative array, the key being the two-letter abbreviation 
of the states, the value being the state name. 

array states(void) 

from functions/db.php 

These are common functions that will help work with MySQL databases. 

DBCONNECTO 

Creates a connection to a MySQL server and selects a database. Defaults are, from 
left to right, "test", "nobody", "", "localhost". If the connection fails, an error mes- 
sage prints and the script ends. 

void dbconnect ([string database name [, string user name [, string 
password [, string server name]]]]) 



Appendix G: Helpful User- Defined Functions 519 

SAFE_QUERY() 

This function will return a result identifier if a mysql query( ) runs successfully; 
otherwise it will print the mysql_error( ) message, the error number, and the query 
text of the query sent to the function. 

int safe_query (string query) 

SET_RESULT_ VARIABLES () The sole argument of this function should be the 
identifier returned as the result of a mysqi_query( ) (or if you are using these func- 
tions, safe_query( )). The query should have returned only a single row. Each col- 
umn returned for the row is turned into a global variable, with the column name 
being the variable name and the result being the variable value. 

void set_resul t_vari abl es (int result identifier) 

fetch_record( ) 

int fetch_record (string table name, mixed key, mixed value) 

This function will select values from the MySQL table specified by the first argu- 
ment. If the optional second and third arguments are not empty, the select will get the 
row from that table where the column named in the second argument has the value 
given by the third argument. The second and third arguments may also be arrays, in 
which case the query builds its where clause using the values of the second argument 
array as the table column names and the corresponding values of the third argument 
array as the required values for those table columns. If the second and third argu- 
ments are not empty, the data from the first row returned (if any) are set to global 
variables by the set_resui t_vari ablest ) function (described previously). 

DB_VALUES_ARRAY() This function builds an associative array out of the values 
in the MySQL table specified in the first argument. The data from the column 
named in the second argument will be set to the keys of the array. If the third argu- 
ment is not empty, the data from the column it names will be the values of the 
array; otherwise, the values will be equal to the keys. If the third argument is not 
empty, the data will be ordered by the column it names; otherwise, they will be 
ordered by the key column. The optional fourth argument specifies any additional 
qualification for the query against the database table; if it is empty, all rows in the 
table will be retrieved. 

If either the first or second argument is empty, no query is run and an empty 
array is returned. The function presumes that whoever calls it knows what they're 
about, e.g., that the table exists, that all the column names are correct, etc. 

array db_val ues_array ([string table name [, string value field [, 
string label field [, string sort field [, string where clause]]]]]) 



520 Part V: Appendixes 



from functions/html.php 



These functions create common HTML elements, including anchors and unord- 
ered lists. 

FONT_TAG() 

This function creates an HTM L font tag. Default size is 2, default font face is sans- 
serif. Any additional attributes in the third argument will be added to the tag. It is 
expecting an associative array, the key of which will be the name of the attribute; 
the value of the array element will be the attribute value. 

string font_tag ([int size [, string typeface [, array 
attributes]]]) 

ANCHOR_TAG() 

This function creates an HTM L anchor tag. The first argument is the href value, the 
second is the string to be surrounded by the anchor. It is expecting an associative 
array, the key of which will be the name of the attribute; the value of the array ele- 
ment will be the attribute value. 

string anchor_tag ([string href [, string text [, array 
attributes]]]) 

IMAGE_TAG() 

This function returns an HTML image tag (<img>). The first argument gives the URL 
of the image to be displayed. Additional attributes may be supplied as an array in 
the third argument. 

string image_tag ([string src [.array attributes]]) 

SUBTITLEO 

This function returns an HTM L <h3> tag. It is used for the titles of secondary areas 
within pages in our examples. The reason to display these via a function, rather 
than just literal <h3> tags, is to enable you to change the format of these subtitles 
in one place, instead of in each script. 

string subtit]e(string string) 

PARAGRAPH () 

This function will return a string inside HTML paragraph (<p>) tags. Attributes for 
the <p> tag may be supplied in the first argument. Any additional arguments will 
be included inside the opening and closing <p> tags, separated by newlines. 

string paragraph ([array attributes [, mixed ...]]) 



Appendix G: Helpful User- Defined Functions 521 

UL_LIST() 

This function returns an HTML unordered (bulleted) list (<ul> tags). If the argu- 
ment is an array, then each value from the array will be included as a list item 
(<i i>) in the list. Otherwise, the argument will simply be included inside the <ui > 
tags as is. 

string ul_l i st(mixed values) 

From functions/forms.php 

These functions create all common form elements, as well as the opening and clos- 
ing <form> tags. 

START_FORM() 

This function returns an HTM L <f orm> tag. If the first argument is empty, the value 
of the global Apache variable SCRIPT_NAME is used for the 'action' attribute of the 
<form> tag. Other attributes for the form can be specified in the optional second 
argument; the default method of the form is "post". The behavior of this function on 
servers other than Apache is not known. It's likely that it will work, as SCRIPT_ 
NAM E is part of the CGI 1.1 specification. 

string start_form ([string action, [array attributes]]) 

END_FORM() 

This function returns a closing form tag. 

string end_f orm( voi d) 

TEXT_FIELD() 

Returns an HTM L <i nput type=text> form element. Default size is 10. 

string text_fie]d ([string name [, string value [, int size [, int 
maximum length]]]]) 

TEXTAREA_FIELD() 

This function returns an HTM L textarea field. The default size is 50 columns and 10 
rows, and the default wrap mode is 'soft', which means no hard newline characters 
will be inserted after line breaks in what the user types into the field. The alterna- 
tive wrap mode is 'hard', which means that hard newlines will be inserted. 

string textarea_f i el d( [stri ng name [, string value [, int cols [, 
int rows [, string wrap mode]]]]]) 



522 Part V: Appendixes 



PASSWORD, Fl EL D() 

This function returns an HTML password field. This is like a text field, but the value 
of the field is obscured (only stars or bullets are visible for each character). The 
default size of the field is 10. A starting value and maximum data length may be 
supplied. 

string password_f i el d ([string name [, string value [, int size [, 
int maximum length]]]]) 

HIDDENFIELDO 

This function returns an HTML hidden form element. A name and value may be 
supplied. 

string hidden_field ([string name [, string value]]) 

FILE FIELD() 

This function returns an HTM L file field form element. 

string f il e_fiel d( [string name]) 

This function returns an HTML file field. These are used to specify files on the 
user's local hard drive, typically for uploading as part of the form. (See http:// 

www.zend.com/manual/features.file-upload.php for more information about 

this subject.) 

SUBMITFIELDO 

This function returns an HTML submit field. The value of the field will be the string 
displayed by the button displayed by the user's browser. The default value is 
"Submit". 

string submi t_f i el d ([string name [, string value]]) 

IMAGEFIELDO 

This function returns an HTML image field. An image field works likes a submit 
field, except that the image specified by the URL given in the second argument is 
displayed instead of a button. 

string image_field ([string name [, string src [, string value]]]) 

RESET_FIELD() 

This function returns an HTML reset form element. 

string reset_field ([string name, [string value]]) 



Appendix G: Helpful User- Defined Functions 523 

CHECKBOX_FIELD() 

This function returns an HTM L checkbox field. The optional third argument will be 
included immediately after the checkbox field, and the pair is included inside an 
HTML <nobr> tag— meaning that they will be displayed together on the same line. 
If the value of the second or third argument matches that of the fourth argument, 
the checkbox will be 'checked' (i.e., flipped on). 

string checkbox_f i el d ([string name [, string value [, string label 
[ , string match]] ] ] ) 

RADIO_FIELD() 

This function returns an HTM L radio button field. The optional third argument will 
be included immediately after the radio button, and the pair is included inside an 
HTML <nobr> tag— meaning that they will be displayed together on the same line. 
If the value of the second or third argument matches that of the fourth argument, 
the radio button will be 'checked' (i.e., flipped on). 

string radi o_f iel d ([string name [, string value [, string label [, 
string match]]]]) 

SELECT_FIELD() 

This function returns an HTML select field (a popup field). If the optional second 
argument is an array, each key in the array will be set to the value of an option of 
the select field, and the corresponding value from the array will be the displayed 
string for that option. If the key or the value from the array matches the optional 
third argument, that option will be designated as the default value of the select 
field. 

string select_field ([string name [, array items [, string default 
val ue]]] ) 

DB_SELECT_FIELD() 

This function returns an HTM L select field (popup field), based on the values in the 
MySQL database table specified by the second argument, as returned by the 

db_vai ues_array( ) function (defined previously). 

string db_sel ect_f i el d ([string name [, string table name [, string 
value field [, string label field [ , string sort field [, string 
match text [, string where clause]]]]]]]) 

DB_RADIO_FIELD() 

This function returns a list of HTML radio button fields, separated by a non- 
breaking space HTML entity ( ) and a newline, based on the values in 
the MySQL database table named by the second argument, as returned by the 

db_vai ues_array( ) function (defined previously). 



524 Part V: Appendixes 



string db_radi o_f i el d (string name, string table name, string value 
field, string label field, string sort field, [string match text], 
[string where clause]) 



From functions/tables.php 



These functions create opening and closing <tabie> tags, as well as <tr> and 
<td> tags. 

START_TABLE() 

This function returns an opening HTML <table> tag, inside an opening paragraph 
(<p>) tag. Attributes for the table may be supplied as an array. 

string start_tabl e( [array attributes]) 

END_TABLE() 

This function returns a closing table tag. 

string end_tabl e( voi d) 

TABLE_ROW() 

This function returns an HTM L table row (<tr>) tag, enclosing a variable number of 
table cell (<td>) tags. If any of the arguments to the function is an array, it will be 
used as attributes for the <tr> tag. All other arguments will be used as values for 
the cells of the row. If an argument begins with a <td> tag, the argument is added 
to the row as is. Otherwise it is passed to the tabi e_cei i ( ) function and the result- 
ing string is added to the row. 

string table_row ([array attributes], [indefinite number of string 
arguments] ) 

TABLE_CELL() 

This function returns an HTML table cell (<td>) tag. The first argument will be used 
as the value of the tag. Attributes for the <td> tag may be supplied as an array in 
the second argument. By default, the table cell will be aligned left horizontally, and 
to the top vertically. 

string table_cell ([string value [, array attributes]]) 



Appendix G: Helpful User- Defined Functions 525 

Additional Functions Not 
Used in this Book 

Here are a couple of functions that may make dealing with common queries a bit 
easier. 

insertrowO 

This is a generic function to run SQL insert statements. 

function i nsert_row( $tabl e=" " , $atts="") 
t 

i f (empty ( $tabl e) || ! i s_array ( $atts ) ) 

f 

return False; 



e I se 



while (list ($col, $val ) = each ($atts)) 
{ 

//if null go to the next array item 

if ($val=="") 

{ 

continue; 

} 

$col_str , = $col . " , " ; 

if (is_int($val ) || is_double ($val)) 

{ 

$ v a 1 _s t r . = $ v a 1 . " , " ; 

} 

el se 

{ 

$val_str , = " ' $val ' , " ; 



Squery = "insert into Stable 
( $col_str ) 

values($val_str)"; 
//trim trailing comma from both strings 
Squery = str_repl ace( " , ) " , ")", Squery); 



safe_query ( Squery) ; 

return mysql_affected_rows( ) ; 



526 Part V: Appendixes 



This function takes two attributes: the first is the table name and the second 
should be an associative array, with the key being the column name and the value 
being the value to be inserted. Single quotes that should surround a string are will 
be included if the variable is not an integer or a double. The function returns FALSE 
if the query fails to perform an action. It will not work in all circumstances, because 
it doesn't check for the column type from the database. But it could be nice for cre- 
ating pages quickly. 

Empty values in the array are not added to the query. For columns left out of the 
query, MySQL will insert either null values or empty strings, depending on whether 
or not the column allows nulls. 

Note that you can create the associative array from a set of variables using the 
compacto function. For example, the following will create an associative array 
named $array, and then insert a row into a table named mytable. It's assumed that 
you will have already connected to the database 

$category=" " ; 

$category_i d=6 ; 

$category_name="my category"; 

$array=compact( "category", "category_id" , "category_name" ) ; 

if ( ! i nsert_row( "mytabl e" , $array)) 

{ 

echo "insert failed"; 



updaterowO 

The function will SQL update statements 

function update_row( $tabl e=" " , $atts="", $where="") 
{ 

i f (empty ($tabl e) || ! is_array($atts ) ) 
{ 

return FALSE; 
) 

el se 
{ 

whiledist ($col, $val ) = each ($atts)) 
{ 

if ($val=="") 
{ 

continue; 
} 
if (is_int($val ) || i s_doubl e( $val ) ) 



Appendix G: Helpful User- Defined Functions 



527 



$str .= "$col=$val , " ; 
elseif($val=="NULL" || $val=="null") 

$str .= "$col=NULL,"; 
el se 

$str .= "$col='$val ' , " ; 



$str = substr($str, 0, -1) ; 
$query = "update Stable set $str"; 
if ( ! empty ( Iwhere) ) 
t 

$query .= " where $where"; 
} 
mysql_query ( $query ) or 

die (mysql_error( ) ) ; 
returr mysql_affected_rows( ) ; 



This function takes three arguments: stable, a string; $atts, an associative 
array containing keys of column names and values of values to be inserted; and 
$where, which is the condition, for example (col umn_id = l). 

Again, this is not robust enough to work in all circumstances. 

delete_row() 

This function takes two arguments: $tabi e, the table name, and $where, the value 
in the where clause. It returns false on failure or if nothing was deleted. 

function del ete_row( $tabl e=" " , $where="") 
t 

if (empty ( $tabl e) || empty ( $where) ) 

{ 

return FALSE; 



$query = "delete from Stable where Iwhere" 
mysql_query($query) or die (mysql_error( ) ) 
return mysql_affected_rows( ) ; 



528 



Part V: Appendixes 



Function selectto _table() 

This function takes a query and lays it out in a simple HTM L table. It assumes that 
a database connection has already been made. 

function s e 1 e c t_t o_t a b 1 e ( $ q u e ry ) 
{ 

Iresul t=mysql_query ( $ query) ; 

$number_cols = mysql_num_fi el ds( Iresul t) ; 

echo "<b>query: $query</b>"; 

//layout table header 

echo "<table border = 1 > \ n " ; 

echo "<tr al i gn = center>\n" ; 

for ($i=0; $i <$number_col s ; $i++) 

{ 

echo "<th>" . mysql_f iel d_name( $resul t , $i). "</th>\n"; 
) 

echo "</tr>\n";//end table header 
//layout table body 

while ($row = mysql_fetch_row( $resul t ) ) 
{ 

echo "<tr al i gn = l eft>\n" ; 
for ($i=0; $i <$number„col s ; $i++) 
{ 
echo " < t d > " ; 

if ( ! i sset( $row[$i ] ) ) //test for null value 

echo "NULL"; 



echo $row[$i ] ; 



echo "</td>\n"; 
) echo "</tr>\n"; 
echo "</table>"; 



en u m_to_ array () 



This functions returns the values defined in an enum field into an array. 



function enum_to_array ( $tabl e=" " , $col = "". 



if (empty($table) || empty ( $col ) ) 



Appendix G: Helpful User- Defined Functions 529 



{ return False; ) 

el se 

{ 

$ query = "describe Stable $col"; 

$result = mysql_query ( $query ) ; 

list( , $col ) = mysql_fetch_array ( $resul t ) ; 

echo $col ; 

if (substr( $col , 0, 4) != "enum") 

{ 

return FALSE; 

} 

$col = str_replace ( ,"" , 

substr( $col , 5 , -1 ) 

); 

$col = expl ode( " , " , $col ) ; 
} 
return $col ; 



You can use the enum field type in MySQL to limit possible values in a column. 
This might be helpful for restricting column values to Y or N, for example. But to 
get at these values in PHP, you need to run one of the MySQL queries that retrieve 
column information. In the preceding example I use the describe query, and I 
assume that the column of interest will be included in the query. 

The query returns 6 columns. In order, they are: Field, Type, Null, Key, Default, 
and Extra. The second, Type, contains the column type— something like 
en um ( 'yes', 'no' ). In the preceding function, this value is assigned to $col . That 
string can then be stripped of the extraneous parentheses and the letters enum. The 
remainder is exploded into an array. 

You can then use the array however you wish, perhaps in a drop-down box. 



Session handling with MySQL 



If you wish to use these, set your session. save_handler to user in your php.ini. This 
set of functions is intended to work with a table that looks something like this: 

create table sessions( 

session_id char(32) not null primary key, 
sess_data text, 
last_update timestamp 
function mysql_sessi on_open( ) 
( 

mysql_pconnect( "1 ocal host" , "root", "") 

or die (mysql_error()); 
$db_sess = mysql_sel ect_db( "test" ) 



530 Part V: Appendixes 

or die (mysql_error( ) ) 



//this function receives the session_id as the only argument 

function mysql_session_read($id) 

{ 

$data = "" ; 
$query = "select sess_data from sessions 
where session_id = '$id'"; 
$result= mysql_query ($query ) or die (mysql_error( ) ) ; 
if ($row = mysql_fetch_row( $resul t) ) 
{ 

$data = sessi on_decode( $row[0] ) ; 
) 

return $data; 
} 

//this takes the session id and the sesssion data 

//as arguments 

function mysql_sessi on_wri te( $i d , $data) 

{ 

$data = sessi on_encode( $data ) ; 

$query = "replace into sessions (session_id, sess_data) 
values ( ' $ i d ' , ' $ d a t a ' ) " ; 

mysql_query ( $ query ) or 
di e(mysql_error( ) ) ; 

return true; 



function mysql_sessi on_cl ose( ) 
{ 

return true; 
} 

//takes only the session id for an argument 

function mysql_sessi on_destroy ( $id) 

{ 

$query = "delete from sessions where session_id = * $ i d ' 
mysql_query ( $ query ) or 
die (mysql_error( ) ) ; 
return true; 
} 



Appendix G: Helpful User- Defined Functions 531 

//this function receives the maximum lifetime setting 

//from php.ini. It is by default set to 1440 seconds. 

//the sessi on .gc_probabi 1 i ty setting in the php.ini determines 

//what percentage of the time this function will run. 

f uncti on mysql_sessi on_gc( $time) 

t 

$query = "delete from sessions where 

last_update < ( subdate(now( ) , 
INTERVAL $time SECOND) )"; 

mysql_query ( $query ) or 

die (mysql_error( ) )_; 
} 
sessi on_set_save_handl er( 

"mysql_sessi on_open" , 

"mysql_sessi on_cl ose" , 

"mysql_sessi on_read" , 

" my s q 1 _s e s s i o n_w r i t e " , 

"mysql_sessi on_destroy " , 

"mysql_sessi on_gc" 
); 



MySQL Backup 



Dan Nedoborski wrote this script, which I planned on including. However, it is a bit 
lengthy for printed form. I recommend you go to his site and take a look for yourself. 

http: //www. ov-m.com/mysqlphpbak/ 



Validation 

Here are a couple of the trickier items to validate properties. 

E- mail validation 

There are a lot of simple regular expressions to make sure a string more or less 
resembles the format of a proper e-mail address, but if you want something that is 
a bit more thorough, try this. It may not be entirely RFC-compliant, but it is pretty 
close. It is included in the /book/functions folder. 



-//CheckEmai 1 








# 








#mai 1 box 


- 


addr-spec 


; simple address 


# 


/ 


phrase route-addr 


; name & addr-spec 



532 



Part V: Appendixes 



#route-addr 


= 


"<" [route] addr-spec ">" 








# 












#route 


= 


1#("@" domain) ":" 


path-rel a 


ti ve 




# 












#addr-spec 


= 


1 ocal -part "@" domai n 


global address 




# 












#1 ocal -part 


= 


word *("." word) 


uni nterpreted 
case-preserved 




# 












#domai n 


= 


sub-domain *("." sub-domain) 








# 












#sub-domai n 


= 


domain-ref / domain-literal 








# 












#domai n-ref 


= 


atom 


symbol i c 


reference 


# 












#atom 


= 


l*<any CHAR except specials, SPACE and CTLs> 




# 












#speci al s 


= 


"C / ")» / ■•<" / ">" / "@" 


Must be in quoted- 




# 


/ 


"," / ";" / ":" / "\" / <"> 


string, 


to use 






/ 


"." / "[" / "]" 


within a 


word . 




if 






( Octal, 


Decimal 


) 


#CHAR 


= 


<any ASCII character) 


( 0-177, 


0.-127 


) 


#ALPHA 


= 


<any ASCII alphabetic character) 






# 






(101-132, 


65.- 90 


) 


# 






(141-172, 


97.-122 


) 


#DIGIT 


= 


<any ASCII decimal digit) 


( 60- 71, 


48.- 57 


) 


#CTL 


= 


<any ASCII control 


( 0- 37, 


0.- 31 


) 


# 




character and DEL> 


( 177, 


127 


) 


#CR 


= 


<ASC 1 1 CR, carriage return) 


( 15, 


13 


) 


#LF 


= 


<ASCII LF, linefeed) 


( 12, 


10 


) 


#SPACE 


= 


<ASCII SP, space) 


( 40, 


32 


) 


#HTAB 


= 


<ASCII HT, horizontal -tab) 


( 11, 


9 


) 


#<"> 


= 


<ASC 1 1 quote mark) 


( 42, 


34 


) 


#CRLF 


= 


CR LF 








# 












#LWSP-char 


= 


SPACE / HTAB 


semanti cs 


= SPACE 




# 












#1 i near -whit 


e-s 


pace = 1*([CRLF] LWSP-char) 


semanti cs 


= SPACE 




# 






CRLF => f 


o 1 d i n g 




# 












#del imi ters 


= 


specials / linear-white-space 


/ comment 






# 












#text 


= 


<any CHAR, including bare 


=> atoms, 


special; 





Appendix G: Helpful User- Defined Functions 533 



CR & bare LF, but NOT 
including CRLF> 



comments and 
quoted-stri ngs are 
NOT recognized. 



quoted-stri ng = <"> *(qtext/quoted-pai r) <">; Regular qtext or 

; quoted chars. 



qtext 



<any CHAR excepting <">, 
"\" & CR, and i ncl udi ng 
linear-white-space> 



; => may be folded 



domain-literal = "[" *(dtext / quoted-pair) "]" 



<any CHAR excluding "[", ; => may be folded 
"]" , "\" & CR, & including 
1 i near-whi te-space> 

"(" *(ctext / quoted-pair / comment) ")" 

<any CHAR excluding "(", ; => may be folded 
")", "\" & CR, & including 
1 i near-whi te-space> 

"\" CHAR 

l*word 

atom / quoted-stri ng 



may quote any char 
Sequence of words 



dtext 

comment 
ctext 

quoted-pair 

phrase 

word 

mailbox = addr-spec 

/ phrase route-addr 

route-addr = "<" [route] addr-spec ">" 

route = 1#("@" domain) ":" 

addr-spec = local -part "@" domain 

val i date_emai 1 C "ins i ght\@bedri jfsnet.nl"); 

unction pri nt_val i date_emai 1 ($eaddr="") 



$result = val i date_emai 1 ( Seaddr ) ? "is valid" : "is not valid" 
print "<h4>email address (". html speci al chars ( $eaddr) ." ) 



; simple address 

; name & addr-spec 

; path-relative 

; global address 



534 Part V: Appendixes 



$result</h4>\n" ; 



function val idate_emai 1 ( $eaddr=" " ) 



if ( empty ( leaddr) ) 
{ 
#print "[$eaddr] is not val i d \ n " ; 
return false; 
} 

lladdr = "" ; 
lladdr = $eaddr; 

# if the addr-spec is in a route-addr, strip away the phrase and <>s 

lladdr = preg_repl ace( ' / A .*</','' , lladdr); 

lladdr = preg_repl ace( ' />.*!/','', II addr ) ; 

if (preg_match( ' / A \@.*: / ' , II addr ) ) #path-rel ati ve domain 

{ 

1 i stddomai n , $addr_spec) = preg_spl i t( '/:/', II addr ) ; 

Idomain = preg_repl ace( ' / A \@/ ' , ' ' , Idomai n) ; 

if (! i s_domai n( Idomai n ) ) ( return false; ) 

lladdr = $addr_spec; 
} 

return ( i s_addr_spec( II addr ) ) ; 
} 

function is_addr_spec ( leaddr = "" ) 
{ 

1 i st($l ocal_part , Idomai n ) = preg_spl it( ' l\@l ', leaddr ) ; 

if ( ! i s_l ocal_part( II ocal_part ) || ! i s_domai n( Idomai n) ) 

{ 
#print "[leaddr] is not valid\n"; 
return false; 

} 

el se 

{ 
#print "[leaddr] is valid\n"; 
return true; 

) 
) 

#local-part = word *("." word) ; uninterpreted 

function i s_l ocal_part ( llocal_part = "" ) 



Appendix G: Helpful User- Defined Functions 535 



if (empty ( $1 ocal_part) ) ( return false; ) 

$bit_array = preg_spl it( ' A . / ' , $1 ocal_part) ; 

while (list(,$bit) = each( $bi t_array ) ) 
t 

if ( ! i s_word( $bi t ) ) ( return false; ) 
} 
return true; 



#word = atom / quoted-string 

#quoted-stri ng = <"> *(qtext/quoted-pai r) <"> 

# 

#qtext = <any CHAR excepting <">, 

# " \ " & C R , a n d i n c 1 u d i n g 

# 1 i near-whi te-space> 
#quoted-pair = "\" CHAR 

function is_word ( $word = "") 



Regular qtext or 

quoted chars. 
=> may be folded 



may quote any char 



if (preg_match( ' / A " .*"$/i ' , $word) ) 
{ 

return ( i s_quoted_stri ng( $word) ) ; 
} 

return ( i s_atom( $word) ) ; 
} 

function i s_quoted_stri ng ( $word = "") 
{ 

$word = preg_repl ace( ' / A "/ ' , ' ' , $word) ; # remove leading quote 

$word = preg_repl ace( '/"$/','', $word) ; # remove trailing 
quote 

$word = preg_repl ace( ' /\\+/ ' , ' ' , $word) ; # remove any quoted- 
pairs 

if (preg_match( ' /\"\\\r/' ,$word) ) # if ", \ or CR, it's bad 
qtext 

1 

return false; 

} 

return true; 



#atom 



l*<any CHAR except specials, SPACE and CTLs> 



536 Part V: Appendixes 



#speci al s 


= 


# 


/ 


# 


/ 


#SPACE 


= 


#CTL 


= 


# 





"(" / ")» / "<" / ">" / "@" 

"," / ";" / ":" / "\" / <"> 
"." / "[" / "]" 
<ASCII SP, space) 
<any ASCII control 
character and DEL> 



; Must be in quoted- 
; string, to use 
; within a word. 
( 40, 32.) 
( 0-37, 0.-31.) 
( 177, 127.) 



function is_atom ( $atom = "") 



if ( 

(preg_match( ' / [\( \ )\<\>\@\ , \ ; \ : \\\"\ . \[\]]/ ' ,$atom)) # 
specials 

|| (preg_matchC A040A ,$atom)) # SPACE 

| (preg_match( ' /[\x00-\xlF]/ ' Jatom)) # CTLs 



return false; 



return true; 



#domain = sub-domain *("." sub-domain] 
#sub-domain = domain-ref / domain-literal 
#domain-ref = atom 
function is_domain ( $domain = "") 



; symbolic reference 



if ( empty ( Sdomai n) ) { return false; } 

# this is not strictly required, but is 99% likely sign of a bad 
domain 

if ( ! preg_match( ' A . / ' , $domai n ) ) ( return false; ) 

$dbit_array = preg_spl i t( '/./', $domai n) ; 

while ( 1 i st( , $dbi t ) = each( $dbi t_array ) ) 

{ 

if ( ! i s_sub_domai n( $dbi t ) ) ( return false; } 

} 

return true; 
} 

function i s_sub_domai n ( $subd = "") 
{ 

if (preg_match( '/ A \[.*\]$/' ,$subd)) //domain-literal 



Appendix G: Helpful User- Defined Functions 537 



return ( i s_domai n_l iteral($subd)); 

} 

return ( i s_atom( $subd) ) ; 
} 

#domai n-1 i teral = "[" *(dtext / quoted-pair) "]" 
#dtext = <any CHAR excluding "[", ; => may be folded 

# "]" , "\" & CR, & including 

# 1 i near-whi te-space> 

#quoted-pair = "\" CHAR ; may quote any char 

function i s_domai n_l i teral ( $dom = "") 

t 

$dom = preg_repl ace( ' /\\+/ ' , ' ' , $dom) ; # remove quoted 
pairs 

if (preg_match( ' / [\[\]\\\r]/ ' , $dom) ) # bad dtext characters 

{ 

return false; 

} 

return true; 



?> 

You would probably want to put all of these functions in one file and then 
include it when needed. It returns 1 (for true) or nothing (for false). You'd probably 
want to use it like so: 

if ( ! val i date_emai 1 ( "myaddress@mydomai n . com" ) ) 
( 

echo "this is not a valid email"; 



Credit- card validation 

Here's the credit-card validator we used in Chapter 14. You can find it in /book/ 
cart/ccval.php. 

/* 

**************************************************************** 



* CCVal - Credit Card Validation function. 

* 

* Copyright (c) 1999 Holotech Enterprises. All rights reserved. 

* You may freely modify and use this function for 

* your own purposes. You may freely distribute it, without 

* modification and with this notice and entire header intact. 



538 Part V: Appendixes 



* This function accepts a credit card number and, optionally, 

* a code for a credit card name. If a Name code is specified, 

* the number is checked against card-specific criteria, then 

* validated with the Luhn Mod 10 formula. Otherwise it is only 

* checked against the formula. Valid name codes are: 
* 

* mcd - Master Card 

* v i s - V i s a 

* amx - American Express 

* dsc - Discover 

* dnc - Diners Club 

* jcb - JCB 
* 

* A description of the criteria used in this function 

* can be found at 

* http://www.beachnet.com/~hstiles/cardtype.html . 

* If you have any 

* questions or comments, please direct them to 

* ccval@holotech.net 

* Alan Little 

* HolotechEnterprises 

* http://www.holotech.net/ 

* September 1999 
* 
******************************************************************** 



function CCVal($Num, $Name = 'n/a') 
{ 

// You can't get money from an empty card 
if (empty($Num) ) ( return FALSE; ) 

// Innocent until proven guilty 

IGoodCard = TRUE; 
//print "<h4>pre-code: GoodCard is ".(IGoodCard ? "TRUE" : 
"FALSE" )."</h4>\n"; 

// Get rid of any non-digits 

$Num = ereg_repl ace( " [ A [ :di gi t : ]] " , "", $Num) ; 

// Perform card-specific checks, if applicable 
switch ($Name) 
( 



Appendix G: Helpful User- Defined Functions 539 



case "mcd" : 

$GoodCard = ereg( " A 5[l-5] . ( 14}$" , $Num); 
break ; 

case "vis" : 

$GoodCard = ereg( " A 4. ( 15 ) $ | A 4 . ( 12}$" , $Num); 
break ; 

case "amx" : 

$GoodCard = ereg( " A 3[47] . { 13) $" , $Num); 
break ; 

case " d s c " : 

$GoodCard = ereg( " A 6011 . { 12)$" , $Num); 
break ; 

case " d n c " : 

$GoodCard = ereg( " A 30[0-5] . { 11 }$ j A 3[68] . ( 12 ) $" , $Num) 
break ; 

case " j c b " : 

$GoodCard = ereg( " A 3. ( 15 ) $ | A 2131 1 1800 . ( 11 )$" , $Num); 
break; 
) 
//print "<h4>pre-l uhn : GoodCard is ".($GoodCard ? "TRUE" : 
"FALSE"). "</h4>\n"; 

// The Luhn formula works right to left, so reverse the number. 
$Num = strrev( $Num) ; 

$Total = 0; 

for ($x=0; $x<strlen($Num) ; $x++) 

{ 

$digit = substr($Num,$x,l) ; 

// If it's an odd digit, double it 
if ($x/2 != floor($x/2)) 
{ 

$digit *= 2; 

// If the result is two digits, add them 

if (strlen($digit) == 2) 
{ 

$digit = substr($digit,0,l) 



540 Part V: Appendixes 

+ substr($digit,l ,1) 

) 
} 

// Add the current digit, doubled and added if applicable, to the 

Total 

$Total += $digit; 



// If it passed (or bypassed) the card-specific check and the Total 

i s 

// evenly divisible by 10, it's cool! 

//print "<h4>post-l uhn: Total = $TotaK/h4>\n" ; 

if (IGoodCard && $Total % 10 == 0) return TRUE; else return 
FALSE; 



?> recurse_ directory 



I wrote the following to help me take a look at all the documents installed on my 
Web server. It will print every document and provide a link to these documents. 

f uncti on recurse_di rectory($path="") 
{ 

global $D0CUMENT_R00T, $HTTP_H0ST 

if (empty($path)) { $path = $D0CUMENT_R00T; } 

if ( ! is_dir($path) ) 

{ 

return FALSE; 



$di r = opendi r( $path) ; 

echo " < u 1 > " ; 

while ( $ f i 1 e = readdir($dir)) 

{ 

if ($file != "." && $file != ". .") 

{ 

if (is_dir($path . "/" . $ f i 1 e ) ) 



Dath; 



Appendix G: Helpful User- Defined Functions 541 

echo "<li><b>$file</bX/li>"; 

recurse_di rectory ( $path . "/" . $file, $base_url , 



e I se 



$url = "http://$HTTP_HOST/$path/$file"; 

$url = str_replace("$DOCUMENT_ROOT", "", $url ) 

echo "<li> <a href = \"$url \">$f i 1 e</a></l i >" ; 



echo " < / u 1 > " ; 
} 

recurse_di rectory ( ) ; 
?> 



Appendix H 

PHP and MySQL Resources 



This appendix presents some resources that should be extremely useful in increasing 
your knowledge of both PHP and MySQL. 



PHP Resources 

Here are some sights that are great for all things PHP. 

PHP site 

This site, located at http://php.net, along with its many international mirrors, 
should be your home away from home. From the home page, you can search the 
manual or one of the many mailing lists. Among the many helpful resources are: 

♦ PHP Annotated Manual —(http://www.php.net/manual /) The online 
manual is really terrific; it includes user comments, some of which clarify 
the use of some of the trickier functions in PHP. 

♦ Downloads— (http://www.php.net/downloads) Hereyou can find 
not only the various distributions, but an HTML manual that you can 
download and put on your local machine. 

♦ Daily snapshots— (http://snaps.php.net) PHP is an active open- source 
project, and features and bug fixes are constantly added to the code base. 
Prior to official releases, you can get the most up-to-date code here. 
Source code is updated daily. Note this is best for the true hacker with a 
box devoted to development. If you have room for only one installation, 
get the most recent source code. A link to the most recent source is always 
on the home page of http://www.php.net/. 

♦ Bug database— (http://bugs.php.net) Wondering if there is a problem 
with a function? Head over to this site to search through the bug reports. 
This is also a place where you can add bug reports. But be very sure that 
you've found a bug before submitting a report. 

♦ FAQ: (http://www.php.net/FAQ.php) Before you post to any mailing list 
or start writing an application, read the FAQ. 



543 



544 



Part V: Appendixes 



PHP mailing lists 



One of the great things about the Web, and about open source projects in particular, 
is the quality of the advice available on the mailing lists. There are many lists, cover- 
ing many specific topics. The ones discussed in this section are all part of php.net 
and use the i ists.php.net mail domain. You can subscribe to any of these lists on 
http://www.php.net/support.php, and they are all archived at http://marc. 
theaimsgroup.com/. The core developers do monitor the list, and respond to ques- 
tions and complaints. 



If you want to keep up with the goings-on of any of the lists but would 
rather not stuff up your mail box, you can also get to these mailing lists via a 
newsgroup reader.Just connect to news .php.net. 




PHP General —This is the generic support area. Over the course of a 
typical day over 100 e-mails are posted to this list. It is amazingly 
helpful, even if you don't have an interest in posting questions or 
supplying answers. Your comrades have some interesting techniques 
and knowledge, which they share daily. 




Please practice good etiquette in posting to the mailing lists. First checkone of 
the searchable archives to make sure your question is something resembling 
unique.And please,read the FAQ 



♦ Database List— This one is a natural for most everyone reading this book. 

♦ Installation List— If you are having problems getting PHP installed on 
your box, this is the place to go. 



Zend.com 

At the core of the PHP is the Zend engine. This engine was built by Zeev Suraski 
and Andi Gutmans. Their work is now the basis for a company that is offering 
products that make PHP even more powerful. By the time you are reading this 
book, it is likely that Zend products will include a cache, which could really 
increase speed, an optimizer, which could help make badly written code run faster, 
a compiler, which would make PHP unreadable (this is great if you're planning on 
distributing code that you would rather not be open source), and an Integrated 
Development Environment. And who wouldn't want that? 



Appendix H: PHP and MySQL Resources 545 

The Zend.com site includes some valuable resources: 

♦ Change list (http://zend.eom/zend/l i st_change_4. php)— Keep up on 
the evolution of PHP 4 here. 

♦ Code gallery (http://zend.com/codex.php)— This is one of the better 
code galleries out there. Browse and see if there are functions that will 
make your life easier. 

♦ Applications (http://zend.com/apps.php)— What? What you have here 
isn't enough? 

♦ Tutorials (http://zend.com/zend/tut/)— Zend has a growing number 
of very informative tutorials that cover a variety of topics. 

PHPBuilder.com 

PHPbuilder is without question one of the best resources for PHP developers. Tim 
Perdue, who runs PHPbuilder, has a built a great base of articles that cover topics that 
include databases, cascading stylesheets, and other topics of interest to developers 
who work in the Web environment. 

PHPbuilder also has discussion boards, job boards, and a code library. It is really 
worth checking with frequently. 

PHPwizard.com 

Earlier in the book, we recommended the phpmyadmin, a PHP tool for Web-based 
administration of MySQL. There are several other useful tools from Tobias Ratschiller 
and Till Gerken. Additionally, from their site you can find other great applications, 
including a Web-based administrative interface to PostGres, a Web-based e-mail 
client, and an add rotation application. There's a bunch of other good stuff available 
on their site as well. 



phpmyadmin is included on the CD-ROM that accompanies this book. 




PEAR 

PEAR stands for the PHP Extension and Application Repository. It is a set of code 
being written by some very skilled programmers who are trying to come up with a 
common set of well-written extensions the rest of us can incorporate into our own 
PHP applications. Stig Bakken, one of the core developers, is heading up the project. 



546 



Part V: Appendixes 



At this point the only place to find PEAR code is the /pear directory of your PHP 
installation. As of the writing of this book, there were several components in PEAR, 
most of which were still undergoing quite a bit of work. There is a database 
abstraction layer, an XML processing class, and some code for directory searching. 
I suggest you browse the documentation in your own installation, and every now 
and then look in on the latest goings-on at snaps.php.net. 

PHPclasses 

A Portuguese programmer named Manual Lemos is among the most prolific 
PHP coders on the planet, and— God bless him— he shares his code at http:// 
phpclasses.upperdesign.com. In fact, PHPclasses is now a code repository for any- 
one who has classes to share with the PHP world. The following are of particular note: 

♦ Manual's Form Processing Class. This class provides a uniform method for 
creating and validating forms. It accounts for about every type of validation 
imaginable. 

♦ Metabase— This is a very complete database abstraction layer. 

♦ Mail Class. This class came to our attention too late to include on the CD. 
Word is, this makes the sending of e-mail with attachments quite a bit easier. 




Both Manual's Form Processing Class and Metabase are included on this 
book's CD-ROM. 



PHP base library 



The PHP base library has a fairly large user base— and it's no wonder. It's a very 
nice set of tools that you can easily add to your PHP system. Many people origi- 
nally used this library because it was the easiest way to use Sessions in PHP3. Now, 
I would recommend using PHP's built-in session functions. But even so, there are 
authorization routines, user administration functions, and a template class that 
could save you some coding time. 

http://phplib.netuse.de/ 



Binarycloud 



The folks at Binarycloud are working on creating a common code base to help with 
rapid development of PHP applications. So far (as of September 2000) there isn't a 
lot of code to review. But their documentation looks really promising. I'd recom- 
mend checking in on http://www.binarycioud.com to see what they're up to. 



Appendix H: PHP and MySQL Resources 547 



Midgard 



The Midgard project is building a content management system with PHP and 
MySQL. If you need content management, this is definitely worth a look: http:// 
www.midgard-project.com. Or you can just work on the application we created in 
Chapter 12. 

Phorum 

Phorum.org has an excellent discussion server written in PHP and MySQL. You may 
want to compare it to the Application in Chapter 11. 

Weberdev 

Of the many Web development sites that have PHP articles, tutorials and code, 
Weberdev.com is among the most extensive: http://www.weberdev.com/. 



Webmonkey 



Both Brad and J ay have worked at Webmonkey. J ay is a former producer of the site, 
and Brad has written several articles. Check out its PHP-related material at http:// 

ho twi red. lycos. com/webmonkey/programmi ng/php/. 



Heyes Computing 



Richard Heyes has created some pretty cool scripts. Take a look: http: //www. 

heyes-computi ng.net/scripts/index. html. 



MySQL Resources 



There's no shortage of resources here either. I've mentioned mainly Web- based 
resources in this appendix; however, there is one hard-copy MySQL resource that I 
must mention. If this book hasn't covered enough of MySQL for your needs, get 
Paul Dubois' MySQL (New Riders, ISBN: 0-7357-0921-1). It is an excellent book. 



MySQL.com 



Predictably, this is probably the best place to find answers to any questions you might 
have about MySQL. Some specific portions of the site are worth particular note: 

♦ Downloads (http://www.mysq! . com/ down 1 oads/)— This is the place to 
find the latest version of MySQL in all the popular formats, including 
rpms, source code, and Windows binaries. 



548 



Part V: Appendixes 



♦ Contributions (http: //www.mysql . com /downloads/contr ib. html) — 

A lot of developers have put together tools that you might be able to 
use when working with MySQL. Of these, the GUI clients are particularly 
interesting. 

♦ Documentation (http://www.mysq! .com/documentation/)— The online 
manual for MySQL is pretty good, and covers many things that this book 
did not. Chapter 7, the language reference, should be bookmarked on 
your browser. 



Both PHP and MySQL have downloadable HTML manuals. I keep them on 
my local machine so I don't have to connect to the Web every time I have 
a question. 




Mailing lists 



The MySQL mailing list is monitored by many of the core developers. If you have a 
concern about the product and post it on the mailing list, someone who is working 
on the product itself will surely see it. In addition, they're really a very nice bunch 
of guys. Information about subscribing to any of the mailing lists can be found 
here: http://www.mysq! . com/documentati on/l i sts . html . A searchable archive 
of the mailing lists can be found here: http://iists.mysq! .com. 



General Client- Side Resources 

Here are a few of the sites we the authors find ourselves returning to frequently. 

Character entity reference 

About the most comprehensive list we know of is found here: http: //www. 
hclrss. demon. co. u k/ demos/ en t4_f rame . html . 

Netscape's tag reference 

If you are still dealing with the mess that is Netscape 4, this tag reference should be 

of some assistance: http: //devel oper.netscape.com /docs/manual s/htmlgu id/ 
contents . htm. 



Appendix H: PHP and MySQL Resources 549 



CSS reference 



CSS is very cool, but still kind of a pain to work with. This chart, compiled by Eric 
Meyer, is the most complete one of its kind: http://webreview.com/wr/pub/ 
guides/style/mastergrid.html. 



Apache References 

Apache will likely be your Web server, and when you are new to it, it can be tricky. 



Apache.org 



This is the home site for the Apache Software Foundation, which now includes 
many interesting projects. In particular, there are some very cool things happening 
in the XML space. Apache can be opaque when you first come to it, but when you 
grow accustomed to using their documentation, you will see that it really isn't very 
difficult to work with. 



Apachetoday.org 



Quite a few of the Apache developers contribute text to this site. Definitely worth 
a look. 



Appendix I 

MySQL Function Reference 



MySQL has many functions, and only a portion of these was used in the course 
of the applications in this book. You should have a good idea of what MySQL func- 
tions are available, as you may find they come in handy at times. 

String Comparison Functions 

This set of functions should not be confused with PHP's string handling functions. 
Normally, if any expression in a string comparison is case-sensitive, the comparison 
is performed in a case- sensitive way. 

LIKE 

This function conducts a pattern match using basic SQL wildcard characters. 

expr LIKE pat [ESCAPE 'escape-char'] 
RETURNS: int 

With like you can use the following two wildcard characters in the pattern: %, 
which matches any number of characters, even zero characters; and _, which 
matches exactly one character. To test for literal instances of a wildcard character, 
precede the character with the escape character. If you don't specify the ESCAPE 
character, \ is assumed. This function returns 1 (true) if the pattern is found or 
(false) if not. 

mysql> select 'jay greenspan' like 'jay%'; 
+ + 

'jay greenspan' like 'jay%' 
+ + 

I 1 I 
+ + 

1 row in set (0.00 sec) 



551 



552 Part V: Appendixes 



REGEXP 



This function performs a pattern match of a string expression (expr) against a regular 
expression (pat). See Appendix C for a discussion of regular expressions. But be aware 
that MySQL does not support regular expressions to the extent you will find in PHP. 

expr REGEXP pat 

or 

expr RLIKE pat 
RETURNS: int 

REGEXP returns 1 (true) if the pattern is found or (false) if not. 

mysql> select name from guestbook where name regexp ' A j.*g'; 
_i + 

| name 

_i + 

Jay Greenspan | 

Jay Green 
_i + 

2 rows in set (0.00 sec) 
mysql > 

STRCMP 

STRCMP(exprl ,expr2) (used in examples) 
RETURNS: int 

Returns if the strings are the same, -1 if the first argument is smaller than the 
second, and 1 if the second argument is smaller than the first. 

mysql> select strcmp( ' foo ' , 'bar'); 
_i + 

strcmp( ' foo ' , ' bar ' ) | 
_i + 

I 1 I 
_i + 

1 row in set (0.11 sec) 

mysql) select strcmp( ' bar ' , 'bar'); 
_i + 



Appendix I: MySQL Function Reference 553 



| strcmp( ' bar ' , ' bar ' ) j 
+ + 

I I 
+ + 

1 row in set (0.00 sec) 

mysql> select strcmp( ' bar ' , 'foo'); 
+ + 

| strcmp( ' bar ' , ' foo ' ) 
+ + 

I -1 I 
+ + 

1 row in set (0.00 sec) 
mysql > 

Cast Operators 

There is only one cast operator you will encounter in MySQL. 

Binary 

BINARY 
RETURNS: 1 

The binary operator casts the string following it to a binary string. This is an 
easy way to force a column comparison to be case-sensitive even if the column 
isn't defined as binary or blob, binary was introduced in MySQL 3.23.0. 

mysql> select bi nary (' Foo ' ) = 'foo', bi nary (' Foo ' ) = 'Foo'; 
_i 1_ 1_ 

| bi nary (' Foo ' ) = 'foo' ( bi nary (' Foo ' ) = 'Foo' 
_i 1_ 1_ 

I | 1 | 
_i 1_ 1_ 

1 row in set (0.06 sec) 

Control Flow Functions 

There are two functions that allow for varying results depending on conditions. 



554 



Part V: Appendixes 



IFNULL 

IFNULLCexprl ,expr2) (used in examples) 
RETURNS: type of exprl 

If exprl is not null, ifnullc ) returns exprl; otherwise, it returns expr2. I fnullc 
returns a numeric or string value depending on the context in which it is used. 

mysql> select ifnul 1 (1/0, 'exp 1 is null'); 
_i + 

| ifnul 1 (1/0, 'exp 1 is null' ) | 
_i + 

exp 1 is null 
_i + 

1 row in set (0.00 sec) 

mysql> select ifnull(l/l, 'exp 1 is not null'); 
_i + 

ifnull(l/l, 'exp 1 is not null') | 



1.00 



1 row in set (0.00 sec) 



IF 

I F(exprl ,expr2 ,expr3) (used in examples) 

If exprl is TRUE (exprl <> o and exprl <> null) then I F( ) returns expr2; 
otherwise, it returns expr3. IF( ) returns a numeric or string value depending on 
the context in which it is used, exprl is evaluated as an integer value, which 
means that if you are testing floating-point or string values, you should do so using 
a comparison operation. 



mysql> select if(name like 'jay%' 
-> from guestbook; 



'Yes' 



Jo ' ) as 'day Names ' 



day Names 



es 

es 

o 

es 

o 

o 



Appendix I: MySQL Function Reference 555 

I No | 

+ h 

10 rows in set (0.00 sec) 

Mathematical Functions 

All mathematical functions return null in case of an error. 

ABS 

This function returns the absolute value of x. 

ABS(X) 

RETURNS: type of X 

SIGN 

This function returns the sign of the argument as -1, 0, or 1, depending on whether 
x is negative, 0, or positive. 

SIGN(X) 

RETURNS: int 

mysql> select sign(lO), sign (-10), sign(0); 

+ h h h 

| sign(lO) | s i g n ( - 1 ) | sign(0) | 
+ h h h 

I i I -i I I 

+ H H h 

1 row in set (0.00 sec) 

MOD 

Modulo is like the % operator in C). It returns the remainder of n divided by m. 

M0D(N,M) or N % M 

RETURNS: int 

mysql> select mod(10,3), mod(10,4); 

+ h h 

| mod(10,3) | mod(10,4) | 
+ h h 

I 1 I 2 | 

+ + + 

1 row in set (0.05 sec) 



556 Part V: Appendixes 



FLOOR 

This function returns the largest integer value not greater than x. 



FLOOR(X) RETURNS: int 

mysql> select floor (8. 5); 

_i h 

floor(8.5) | 

H h 

I 8 | 

_i h 

1 row in set (0.00 sec) 

CEILING 

This function returns the smallest integer value not less than x. 

FUNCTION: CEILING(X) 

RETURNS: int 

mysql> select ceil ing(8.5) ; 

h + 

c e i 1 i n g ( 8 . 5 ) | 

H + 

I 9 I 
_i + 

1 row in set (0.00 sec) 

Round 

This function returns the argument x, rounded to an integer. 

Round R0UND(X [,D]) 
RETURNS: mixed 

Returns the argument x, rounded to a number with D decimals. If D is 0, or does 
not exist, the result will have no decimal point or fractional part. 

mysql> select round(8.53), round(8.47), round(8. 534 , 2) ; 
_i h H h 

round(8.53) | round(8.47) | round(8. 534 , 2) 

H h H h 

| 9 | 8 | 8.53 | 

H h H h 

1 row in set (0.33 sec) 



Appendix I: MySQL Function Reference 557 



TRUNCATE 



truncate returns the number x, truncated to d decimals. If D is 0, the result will 
have no decimal point or fractional part. 

TRUNCATE(X.D) 
RETURNS: decimal 

Example: 

mysql> select truncate(8. 53 , 0) , truncate(8.43 ,0) , truncate(8. 534 , 2) ; 
+ _| _| + 

| truncate(8.53,0) | truncate(8.43 ,0) | truncate(8. 534 , 2) | 
+ _| H + 

| 8 | 8 | 8.53 | 
+ + + + 

1 row in set (0.05 sec) 

EXP 

This function returns the value of e (the base of natural logarithms) raised to the 
power of x. 

EXP(X) 
RETURNS: float 

LOG 

This function returns the natural logarithm of X. If you want the log of a number x 
to some arbitrary base B, use the formula log(X)/log(B). 

L0G(X) 
RETURNS: float 

LOG10 

logio returns the base- 10 logarithm of x. 



LOG10(X) 
RETURNS: float 



558 Part V: Appendixes 



POW(X,Y) or POWER(X,Y) 

This function returns the value of x raised to the power of Y. 



RETURNS: float 



SQRT 

This returns the non- negative square root of x. 



SQRT(X) 
RETURNS: 


float 












PI 














This returns an approx 


imation of Pi. 








PiO 
RETURNS: 


float 












COS 














cos returns the cosine 


of X, 


where x 


is given 


in 


radians 


COS(X) 
RETURNS: 


float 













SIN 

sin returns the sine of x, where x is given in radians. 



SIN(X) 
RETURNS: float 



TAN 

This returns the tangent of x, where x is given in radians. 



TAN(X) 

RETURNS: float 



ACOS 

This function returns the arc cosine of x— that is, the value whose cosine is x. It 
returns null if x is not in the range -1 to 1. 



Appendix I: MySQL Function Reference 559 

ACOS(X) 
float 

ASIN 

This returns the arc sine of x— that is, the value whose sine is x. Returns null if x 
is not in the range -1 to 1. 

ASIN(X) 
RETURNS: float 

ATAN 

atan returns the arc tangent of x —that is, the value whose tangent is x. 

ATAN(X) 
RETURNS: float 

ATAN 2 

ATAN2 returns the arc tangent of the two variables x and Y. The process is similar to 
calculating the arc tangent of Y/x, except that the signs of both arguments are used 
to determine the quadrant of the result. 

ATAN2(X,Y) 
RETURNS: float 

COT 

This function returns the cotangent of x. 

COT(X) 
RETURNS: float 

RAND 

This function returns a random floating-point value in the range to 1.0. 

RANDO 

or 

RAND(N) 
RETURNS: float 



560 Part V: Appendixes 



If an integer argument n is specified, it is used as the seed value. You cant use a 
column with randc ) values in an 'order by clause because order by would evaluate 
the column multiple times. In MySQL 3.23, you can, however, do: select * from 
table_name order by rand(). This is useful to get a random sample. Note that a 
randc ) in a WHERE clause will be re-evaluated every time the WHERE is executed. 

LEAST 

With two or more arguments, this function returns the smallest (minimum-valued) 
argument. 

LEAST(X,Y ) 

RETURNS: type of X 

mysql> select 1 east( 2,7,9,1) ; 
_i h 

least(2,7,9,l) | 
_i h 

I 1 I 
_i h 

1 row in set (0.00 sec) 

GREATEST 

greatest returns the largest (maximum-valued) argument. In MySQL versions 
prior to 3.22.5, you can use max( ) instead of greatest. 

GREATEST(X,Y ) 

RETURNS: type of X 

mysql> select greatest(2,7,9,l); 
_i + 

greatest(2,7,9,l) | 
_i + 

I 9 
H + 

1 row in set (0.00 sec) 

DEGREES 

This returns the argument X, converted from radians to degrees. 

DEGREES(X) 
RETURNS: float 

RADIANS 

This returns the argument x, converted from degrees to radians. 



Appendix I: MySQL Function Reference 561 



RADIANS(X) 

RETURNS: float 



String Functions 



MySQL's string functions return null if the length of the result would be greater than 
the max_allowed_packet server parameter. This parameter can be set by starting 
MySQL with a command like this: 

safe_mysqld -0 max_al 1 owed_packet=16M 

For functions that operate on string positions, the first position is numbered 1. 

ASCII 

Returns the ASCII code value of the leftmost character of the string str. Returns 
if str is the empty string. Returns null if str is null. 

ASCII(str) 

RETURNS: int 

mysql> select ascii('\n'); 

+ + 

| a s c i i ( ' \ n ' ) | 
+ + 

I io I 
+ + 

1 row in set (0.00 sec) 

ORD 

If the leftmost character of the string str is a multi-byte character, this function 
returns the code of multi-byte character by returning the ASCII code value of the 

Character in the format of: ((first byte ASCII code)*256+(second byte ASCII 

code))[*256+third byte ascii code ...]. If the leftmost character is not a 
multi-byte character, ord returns the same value as the similar ascii ( ) function. 

ORD(str) 
RETURNS: int 

CONV 

This function converts numbers between different number bases. 

C0NV(N,from_base,to_base) 
RETURNS: string 



562 Part V: Appendixes 



It returns a string representation of the number n, converted from base from_ 
base to base to_base. It returns null if any argument is null. The argument n is 
interpreted as an integer, but may be specified as an integer or a string. The mini- 
mum base is 2 and the maximum base is 36. If to_base is a negative number, n is 
regarded as a signed number. Otherwise, N is treated as unsigned, conv works with 
64-bit precision. 

mysql> select conv(3 , 10 , 2) ; 
_i + 

conv(3,10,2) | 

H + 

I 11 I 

_l + 

BIN 

This function returns a string representation of the binary value of N, where N is a 
long (bigint) number. It is equivalent to con V(N, io,2). Returns null if n is null. 

BIN(N) 

RETURNS: string 

OCT 

This function returns a string representation of the octal value of N, where N is a 
long (bigint) number. It is equivalent to conv(n,io,8). It returns null if n is 

NULL. 

OCT(N) 

RETURNS: string 

HEX 

This function returns a string representation of the hexadecimal value of N, where N 
is a long (bigint) number. This is equivalent to conv(n,io,16). Returns null if n 

is NULL. 

HEX(N) 

RETURNS: string 

CHAR 

This function interprets the arguments as integers and returns a string consisting of 
the ASCII code values of those integers, null values are skipped. 



Appendix I: MySQL Function Reference 563 



CHAR(N ) 

RETURNS: string 

CON CAT 

This function returns the string that results from the concatenation of the arguments. 
It returns null if any argument is null, concat may have more than two arguments. 
A numeric argument is converted to the equivalent string form. 

CONCAKstrl ,str2 , . . . ) (used in examples) 
RETURNS: string 

This function is used in the following example to prepend a wildcard character 
onto the column in the where clause of a query. 

select 1 Trom bl ocked_domai ns 

where ' $REM0TE_H0ST ' 1 i ke concat ('%', domai n ) 
and release_dt is null 

LENGTH 

This function returns the length of the string str. Note that for char_length( ), 
multi-byte characters are only counted once. 

LENGTH(str) 

or 

CHAR_LENGTH(str) 

RETURNS: int 

mysql> select 1 ength( 'mysql functions'); 
+ h 

| 1 ength( 'mysql functions') | 
+ h 

I 15 I 
+ h 

1 row in set (0.00 sec) 

LOCATE 

This function returns the position of the first occurrence of substring substr in 
string str. Returns if substr is not in str. The optional third argument allows 
you to specify a starting position for the search. 



564 Part V: Appendixes 



LOCATE(substr ,str [,pos]) 
or 

POSITIONCsubstr IN str) 
RETURNS: int 

The optional third argument specifies an offset to start the search. 

mysql> select locateC's', 'mysql funcitons') as examplel, 

-> locateC's', 'mysql f unci tons ' ,4) as example2; 
_i H h 

| examplel | example2 | 

h h h 

I 3 | 15 | 

H H h 

1 row in set (0.00 sec) 

INSTR 

This function returns the position of the first occurrence of substring substr in 
string str. It is the same as locatec ), except that the arguments are swapped and 
no argument that indicates position is allowed. 

INSTRCstr, substr) 
RETURNS: int 

LPAD 

This function returns the string str, left-padded with the string padstr until str is 
len characters long. 

LPADCstr ,1 en , padstr) 

RETURNS: string 

mysql) select IpadC'foo', 15, 'k'); 
_i + 

IpadC 'foo' , 15, 'k') | 
_i + 

kkkkkkkkkkkkfoo | 

_i + 

1 row in set (0.00 sec) 

RPAD 

This function returns the string str, right-padded with the string padstr until str 
is len characters long. 



Appendix I: MySQL Function Reference 565 

RPAD(str ,1 en .padstr) 
RETURNS: string 

LEFT 

This function returns the leftmost l en characters from the string str. 

LEFT(str.len) 
RETURNS: string 

RIGHT 

This function returns the rightmost len characters from the string str. 

RIGHT(str.len) 
RETURNS: string 

SUBSTRING 

This function returns a substring len characters long from string str, starting at 
position pos, and continuining for len number of characters. The variant 
form that uses from is ANSI SQL92 syntax. 

SUBSTRING(str,pos[,len]) 

or 

SUBSTRING(str FROM pos FOR len) 

or 

MIDtstr , pos , 1 en ) (used in examples) 

RETURNS: string 

mysql> select mid( 'mysql f uncti ons ' ,6 ,8) ; 
+ h 

| mid( 'mysql f uncti ons ' ,6 ,8) | 
+ h 

| function 

+ h 

1 row in set (0.00 sec) 

SUBSTRINGJNDEX 

This function returns the substring from string str after count occurrences of the 
delimiter deiim. If count is positive, everything to the left of the final delimiter 



566 Part V: Appendixes 



(counting from the left) is returned. If count is negative, everything to the right of 
the final delimiter (counting from the right) is returned. 

SUBSTRING_INDEX(str, del im, count) (used in examples) 

RETURNS: string 

mysql> select substri ng_i ndex( 'mysql f uncti onsmysql ' , 'fu', 1); 
_i h 

substri ng_i ndex( 'mysql f uncti ons ' , 'fu', 1) 
_i h 

mysql | 

H h 

1 row in set (0.00 sec) 

mysql> select substri ng_i ndex( 'mysql f uncti onsmysql ' , 'fu', -1); 
_i + 

substri ng_i ndex( 'mysql f uncti onsmysql ' , 'fu', -1) | 
H + 

net ions my sql 
H + 

1 row in set (0.00 sec) 



LTRIM 



LTRIM(str) 
RETURNS: string 



RTRIM 

This function returns the string str with trailing space characters removed. 

RTRIM(str) 
RETURNS: string 

TRIM 

This function returns the string str with all remstr prefixes and/or suffixes 
removed. If none of the specifiers both, leading, or trailing are given, both is 
assumed. If remstr is not specified, spaces are removed. 

TRIM([[B0TH j LEADING | TRAILING] [remstr] FROM] str) (used in 
exampl es ) 
RETURNS: string 

mysql) select trim(both '\n' from '\n mystring'); 
_i + 



Appendix I: MySQL Function Reference 567 

| trim(both '\n' from '\n my string') | 
+ 1_ 

my s t r i n g 
+ 1_ 

1 row in set (0.00 sec) 

Note that remstr will exact match only the exact sequence of characters. So 
putting '\t\n\ in the remstr will remove only occurrences where tabs and newlines 
appear consecutively. 

REPLACE 

This function returns the string str with all occurrences of the string from_str 
replaced by the string to_str. 

REPLACE! str, f rom_str , to_str ) 
RETURNS: string 

SOUNDEX 

This function returns a soundex string from str. 

SOUNDEX(str) 
RETURNS: string 

Two strings that sound "about the same" should have identical soundex strings. 
A "standard" soundex string is four characters long, but the soundexo function 
returns an arbitrarily long string. You can use substring( ) on the result to get a 
"standard" soundex string. All non-alphanumeric characters are ignored in the 
given string. All international alpha characters outside the A-Z range are treated as 
vowels. 

SPACE 

This function returns a string consisting of N space characters. 

SPACE(N) 
RETURNS: string 



REPEAT 



This function returns a string consisting of the string str repeated count times. If 
count <= o, it returns an empty string. It returns null if str or count are null. 

REPEAT(str, count) 
RETURNS: string 



568 Part V: Appendixes 



REVERSE 

This function returns the string str with the order of the characters reversed. 

REVERSE(str) 
RETURNS: string 

INSERT 

This function returns the string str, with the substring beginning at position pos 
and i en characters long replaced by the string newstn. 

INSERT(stn,pos,len,newstr) 

RETURNS: string 

mysql> select i nsert( 'mysql f uncti ons ' , 6,2,'FU'); 
_i + 

i nsert( 'mysql f uncti ons ' , 6,2,'FU') 
_i + 

mysql FUncti ons 
H + 

1 row in set (0.44 sec) 

ELT 

This function returns stri if N = l, str2 if n = 2, and so on. It returns null if n 
is less than 1 or greater than the number of arguments, elk ) is the complement of 

FIELDO. 

ELT(N,strl,str2,str3 ) 

RETURNS: string 

mysql) select elt(2, 'foo', 'bar', 'foobar'); 
H 1_ 

elt(2, 'foo', 'bar', 'foobar') | 
H h 

bar 
H 1_ 

1 row in set (0.00 sec) 

FIELD 

This function returns the index of str in the strl, str2, str3, . . . list. It returns 
if str is not found. field( ) is the complement of elk ). 

FIELD(str,strl,str2,str3, . . . ) 

RETURNS: int 

mysql) select fi el d( ' foobar ' , 'foo', 'bar', 'foobar'); 



Appendix I: MySQL Function Reference 569 



+ + 

| f ield( ' foobar ' , 'foo', 'bar', 'foobar') | 
+ + 

I 3 | 
+ + 

1 row in set (0.01 sec) 

LCASE 

This function returns the string str with all characters changed to lowercase 
according to the current character set mapping (the default is ISO-8859-1 Latinl). 

LCASE(str) or LOWER(str) (used in examples) 
RETURNS: string 

UCASE 

This function returns the string str with all characters changed to uppercase 
according to the current character set mapping (the default is ISO-8859-1 Latinl). 

UCASE(str) or UPPER(str) 
RETURNS: string 

LOAD_FILE 

This function reads the file and returns the file contents as a string. The file must be 
on the server, and you must specify the full pathname to the file. The file must be 
readable by all and smaller than max_ai i owed_packet. If the file doesn't exist or 
can't be read, the function returns NULL. 

LOAD_FILE(file_name) 
RETURNS: string 



Date and Time Functions 

MySQL offers many functions for calculating dates. Of all of the MySQL functions 
available, these are the ones you will probably use most frequently. 

The date_format function will allow you to format dates to take the form of 
MySQL timestamps. In addition, there are several functions that will easily allow 
you to get specific date information from a column. For example, to find the day of 
the week of all of the entires in a timestamp column, you could use the following. 

mysql> select dayname(created) from guestbook; 
+ + 



570 



Part V: Appendixes 



dayname(created) | 
_i + 

Sunday 

Sunday 

Wednesday 

Sunday 

Sunday 

Wednesday 

Wednesday 

Wednesday 

DAYOFWEEK 

This function returns the weekday index for date (1 = Sunday, 2 = Monday, ... 7 
Saturday). These index values correspond to the ODBC standard. 

DAYOFWEEK(date) (used in examples) 

RETURNS: int 

mysql> select dayofweek( ' 2001-01-01 ') ; 
_i h 

dayofweek( '2001-01-01' ) 
H 1_ 

I 2 | 
_i 1_ 

1 row in set (0.33 sec) 

WEEKDAY 

This function returns the weekday index for date (0 = Monday, 1 = Tuesday, ... 6 
Sunday). 

WEEKDAY(date) (used in examples) 
RETURNS: int 

DAYOF MONTH 

This function returns the day of the month for date, in the range 1 to 31. 

DAYOFMONTH(date) 
RETURNS: int 



DAYOFYEAR 



This function returns the day of the year for date, in the range 1 to 366. 



Appendix I: MySQL Function Reference 571 



DAYOFYEAR(date) 

RETURNS: int 

mysql> select dayofmonth( ' 02-01-2000 ') ; 
+ + 

| dayofmontht '02-01-2000' ) 
+ + 

I 20 
+ + 

1 row in set (0.00 sec) 

MONTH 

This function returns the month for date, in the range 1 to 12. 

MONTH(date) 
RETURNS: int 

DAYN AM E 

This function returns the full name of the weekday for date. 

DAYNAME(date) 

RETURNS: string 

mysql> select dayname( ' 10/01/2000 ') ; 
+ h 

| dayname( '10/01/2000' ) | 
_i h 

| Wednesday 

+ h 

1 row in set (0.00 sec) 

MONTHNAME 

This function returns the full name of the month for date. 

MONTHNAME(date) 
RETURNS: string 

QUARTER 

This function returns the quarter of the year for date, in the range 1 to 4. 

QUARTER(date) 
RETURNS: int 



572 Part V: Appendixes 



To find all of the people who signed your guestbook in the second quarter of the 
year, you could use this: 

select name from guestbook where quarter(created) = 2; 

WEEK 

With a single argument, this function returns the week for date, in the range to 53. 

WEEKCdate [, first]) 
RETURNS: int 

The optional second argument allows you to specify whether the week starts on 
Sunday or Monday. The week starts on Sunday if the second argument is and on 
M onday if the second argument is 1. 

YEAR 

This function returns the year for date, in the range 1000 to 9999. 

YEAR(date) (used in examples) 
RETURNS: int 

YEARWEEK 

This function returns year and week for a date, in the format YYYYWW. The second 
argument works exactly like the second argument in weekc ). 

YEARWEEK(date [.first]) 
RETURNS: int 

HOUR 

This function returns the hour for time, in the range to 23. 

HOUR(time) 
RETURNS: int 

MINUTE 

This function returns the minute for time, in the range to 59. 

MINUTE(time) 
RETURNS: int 



Appendix I: MySQL Function Reference 573 

SECOND 

This function returns the second for time, in the range to 59. 

SECOND(time) 
RETURNS: int 

PERIOD ADD 

This function adds N months to period P (in the format YYM M or YYYYM M ) and 
returns a value in the format YYYYM M. 

PERI0D_ADD(P,N) 
RETURNS: int 

Note that the period argument p is not a date value. 

mysql> select peri od_add( ' 200006 ' ,7 ) ; 
+ h 

| period_add( '200006' ,7) 
_i 1. 

| 200101 | 
_i 1. 

1 row in set (0.00 sec) 

PERIOD_DIFF 

This function returns the number of months between periods Pi and P2. Pi and P2 
should be in the format YYM M orYYYYMM. 

PERI0D_DIFF(P1,P2) 

RETURNS: int 

Note that the period arguments Pi and P2 are not date values. 

mysql> select peri od_di ff( ' 200106 ',' 200001 ') ; 
+ 1_ 

| period_diTf( '200106' , '200001' ) | 
+ 1_ 

I 17 | 
+ h 

1 row in set (0.00 sec) 



574 



Part V: Appendixes 



DATE_ADD 

These functions perform date arithmetic. They are new for MySQL 3.22. 

DATE_ADD(date, INTERVAL expr type) 

or 

DATE_SUB(date, INTERVAL expr type) 

or 

ADDDATE(date, INTERVAL expr type) 

or 

SUBDATE(date, INTERVAL) (used in examples) 
RETURNS: date 

adddateo and subdateo are identical to date_addo and date_subo. In 
MySQL 3.23, you can use + and - instead of date_addo and date_subo. (See 
example.) date is a datetime or date value specifying the starting date, expr is an 
expression specifying the interval value to be added or substracted from the starting 
date, expr is a string; it may start with a - for negative intervals, type is a keyword 
indicating how the expression should be interpreted. 

Table 1-1 shows how the type and expr arguments are related. 



Table 1-1 DATE_ADD() OPERATORS 


type 


Meaning 


Expected expr format value 


SECOND 


Seconds 


SECONDS 


MINUTE 


Minutes 


MINUTES 


MINUTE_SECOND 


Minutes and seconds 


"MINUTES:SECONDS" 


HOUR 


Hours 


HOURS 


H0UR_SEC0ND 


Hours, minutes, seconds 


"HOURS:MINUTES:SECONDS" 


HOUR_MINUTE 


Hours and minutes 


"HOURSrMINUTES" 


DAY 


Days 


DAYS 


DAY_SECOND 


Days, hours, minutes, seconds 


"DAYSHOURS:MINUTES: 
SECONDS" 



Appendix I: MySQL Function Reference 575 



type 


Meaning 


Expected expr format value 


DAY_MINUTE 


Days, hours, minutes 


"DAYS HOURS:MINUTES" 


DAYJHOUR 


Days and hours 


"DAYS HOURS" 


MONTH 


Months 


MONTHS 


YEAR 


Years 


YEARS 


YEAR_MONTH 


Years and months 


"YEARS-MONTHS" 



MySQL allows any punctuation delimiter in the expr format. The ones shown in 
the table are the suggested delimiters. If the date argument is a date value and 
your calculations involve only year, month, and day parts (that is, no time parts), 
the result is a date value. Otherwise, the result is a datetime value. 

mysql > select '2001-01-01 13:00:00' + interval 10 m 
_i + 

'2001-01-01 13:00:00' + interval 10 minute | 
_i + 

2001-01-01 13:10:00 | 

_i + 

1 row in set (0.39 sec) 

mysql > select '2000-01-01 00:00:00' - interval 1 second; 

_i + 

'2000-01-01 00:00:00' - interval 1 second | 
_i + 

1999-12-31 23:59:59 | 

_i + 

1 row in set (0.00 sec) 

mysql> select date_add( '2000-01-01 00:00:00', interval '1:1:1' hour_second) ; 

_i + 

date_add( '2000-01-01 00:00:00', interval '1:1:1' hour_second) | 
_l + 

2000-01-01 01:01:01 | 

_i + 

1 row in set (0.00 sec) 

mysql> select date_sub( '2000-01-01 00:00:00', interval '1' month); 

_i + 

date_sub( '2000-01-01 00:00:00', interval '1' month) | 
_i + 



576 Part V: Appendixes 



[ 1999-12-01 00:00:00 | 

+ 1_ 

1 row in set (0.00 sec) 

If you specify an interval value that is too short (does not include all the interval 
parts that would be expected from the type keyword), MySQL assumes you have left 
out the leftmost parts of the interval value. For example, if you specify a type of 
day_second, thevalueof expr is expected to have days, hours, minutes, and seconds 
parts. If you specify a value like l : 10, MySQL assumes that the days and hours parts 
are missing and that the value represents minutes and seconds. 

TO_DAYS 

Given a date date, this function returns a daynumber (the number of days since 
year 0). 

TO_DAYS(date) (used in examples) 
RETURNS: int 

to„days( ) is not intended for use with values that precede the advent of the 
Gregorian calendar (1582). Note that this is not the same as the PHP mktime( ) func- 
tion, which gets the date as of January 1, 1970. Seethe MySQL UNIX_TIMESTAMP 
function if you need that information. 

FROM_DAYS 

Given a daynumber n, this function returns a date value. 

FR0M_DAYS(N) (used in examples) 
RETURNS: date 

from_days( ) is not intended for use with values that precede the advent of the 
Gregorian calendar (1582). 

DATE_ FORMAT 

This function formats the date value according to the format string. 

DATE_F0RMAT(date, format) (used in examples) 
RETURNS: string 

The specifiers in Table 1-2 may be used in the format string. 



Appendix I: MySQL Function Reference 577 



Table 1-2 DATE_ FORM AT SPECIFIERS 


Specifier 


Meaning 


%M 


Month name (Januarythrough December) 


%W 


Weekday name (Sundaythrough Saturday) 


%D 


Day of the month with English suffix (1st, 2nd, 3rd, etc.) 


%Y 


Year, numeric, four digits 


%y 


Year, numeric, two digits 


%a 


Abbreviated weekday name (Sun. .Sat) 


%d 


Day of the month, numeric (00. .31) 


%e 


Day of the month, numeric (0..31) 


%m 


Month, numeric (01. .12) 


%c 


Month, numeric (1..12) 


%b 


Abbreviated month name (Jan. .Dec) 


%j 


Day of year (001..366) 


%H 


Hour(00..23) 


%k 


Hour(0..23) 


%h 


Hour (01..12) 


561 


Hour (01..12) 


%1 


Hour(1..12) 


%i 


Minutes, numeric (00. .59) 


%r 


Time, 12- hour (hh: mm: ss [AP]M) 


%T 


Time, 24- hour (hh:mm:ss) 


%S 


Seconds (00..59) 


%s 


Seconds (00..59) 


%p 


AM or PM 


%w 


Day of the week (o=Sunday..6=Saturday) 


%U 


Week (o.. 53), where Sunday is the first day of the week 


%u 


Week (o.. 53), where M onday is the first day of the week 




Continued 



578 Part V: Appendixes 



Table 1-2 DATE FORMAT SPECIFIERS (Continued) 



Specifier Meaning 

%v Week (1..53), where Sunday is the first day of the week; used with %x 

%v Week (1..53), where M onday is the first day of the week; used with %x 

%x Year for the week, where Sunday is the first day of the week, numeric, 

four digits, used with %v 

%x Year for the week, where M onday is the first day of the week, numeric, 

four digits, used with %v 

%% A literal % 



All other characters are just copied to the result without interpretation. 

mysql> select date_format( ' 2001-01-01 ' , '%W %M %d, %Y ' ) ; 
_i + 

date_format( '2001-01-01' , '%W %M %d, %Y ' ) | 
H + 

Monday January 01, 2001 | 

H + 

1 row in set (0.00 sec) 

mysql> select date_format( ' 2001-01-01 15:30:20', 

->'%W %M %d, %Y % I : % 1 : % S %p ' ) ; 
_i 1_ 

date_format( '2001-01-01 15:30:20', '%W %M %d, %Y % I : % i : % S %p') | 
_i 1_ 

Monday January 01, 2001 03:30:20 PM | 
_i 1_ 

1 row in set (0.00 sec) 

As of MySQL 3.23, the % character is required before format specifier characters. 
In earlier versions of MySQL, % was optional. 

TIM E_ FORMAT 

This function is used like the date_formak ) function above, but the format string 
may contain only those format specifiers that handle hours, minutes, and seconds. 
If specifiers other than hours, minutes, and seconds are included, the function will 
return a NULL value. 



Appendix I: MySQL Function Reference 579 



TIME_FORMAT(time .format ) (used in examples) 
RETURNS: string 

CURDATE 

This function returns today's date as a value in YYYY-M M -DD or YYYYM M DD for- 
mat, depending on whether the function is used in a string or numeric context. 

CURDATEO or CURRENT_DATE (used in examples) 
RETURNS: mixed 

CURTIME 

This function returns the current time as a value in HH:MM:SS or HHMMSS format, 
depending on whether the function is used in a string or numeric context. 

CURTIMEO or CURRENT_TIME 
RETURNS: mixed 

NOW 

This function returns the current date and time as a value in YYYY-M M-DD 
HH:M M :SS or YYYYM M DDHHM MSS format, depending on whether the function is 
used in a string or numeric context. 

NOWO 

or 

SYSDATEO 

or 

CURRENT_TIMESTAMP (used in examples) 
RETURNS: string 

UNIX_TIMESTAMP 

If this function is called with no argument, it returns a Unix timestamp (seconds 
since 1970-01-01 00:00:00 GMT). If unix^timestampc ) is called with a date argu- 
ment, it returns the value of the argument as seconds since 1970-01-01 00:00:00 
GMT. date may be a DATE string, a DATETIME string, a TIMESTAMP, or a number 
in the format YYMMDD orYYYYMMDD in local time. 

UNIX_JIMESTAMP([date]) 
RETURNS: int 



580 Part V: Appendixes 

FROM_UNIXTIME 

This function returns a representation of the unix_timestamp argument as a value 
in YYYY-MM-DD HH:MM:SS or YYYYMMDDHHMMSS format, depending on 
whether the function is used in a string or numeric context. 

FROM_UNIXTIME(unix„timestamp) (used in examples) 
RETURNS: string 

From Unixtime 

This function returns a string representation of the Unix timestamp, formatted 
according to the format string, format may contain the same specifiers as those 
listed in the entry for the date_format( ) function. 

FR0M_UN IXTI ME ( uni x_timestamp , format ) (used in examples) 
RETURNS: string 

SEC_TO_TIME 

SEC_TO_TIME(seconds) 
RETURNS: string 

Returns the seconds argument, converted to hours, minutes and seconds, as a 
value in HH:MM:SS or HHMMSS format, depending on whether the function is 
used in a string or numeric context. 

TIME_TO_SEC 

TIME_TO_SEC( time) (used in examples) 
RETURNS: int 

This function returns the time argument, converted to seconds. 



Miscellaneous Functions 

Here are a few other functions that don't fit under any of the previous categories. 

Database 

This function returns the current database name. If there is no current database, 
database ( ) returns the empty string. 

DATABASEO 

RETURNS: string 



Appendix I: MySQL Function Reference 581 



User 



This function returns the current MySQL username. In MySQL 3.22.11 or later, this 
includes the client hostname as well as the username. 

USERO 

or 

SYSTEM_USER() 

or 

SESSION_USER( ) (used in examples) 

RETURNS: string 

VERSION 

This function returns a string indicating the MySQL server version. 

VERSIONO 
RETURNS: string 

PASSWORD 

This function calculates a password string from the plaintext password str. 

PASSWORD(str) (used in examples) 
RETURNS: string 

This is the function that encrypts MySQL passwords for storage in the Password 
column of the user grant table. PASSWORD! ) encryption is one-way. passwordo 
does not perform password encryption in the same way that Unix passwords are 
encrypted. You should not assume that if your Unix password and your MySQL 
password are the same, PASSWORD! ) will result in the same encrypted value as is 
stored in the Unix password file. See encrypt( ). 

ENCRYPT 

This function encrypts str using the Unix crypt( ) system call. 

ENCRYPT(str[,salt]) 
RETURNS: string 

The salt argument should be a string with two characters. (As of MySQL 
3.22.16, salt may be longer than two characters.) If crypt( ) is not available on 
your system, encrypt( ) always returns null. encrypt( ) ignores all but the first 
eight characters of str on most systems. 



582 Part V: Appendixes 



ENCODE 

This function encrypts str using pass_str as the password. 

ENCODE(str,pass_str) 
RETURNS: binary string 

To decrypt the result, use decode( ). The result is a binary string. If you want to 
save it in a column, use a blob column type. 

DECODE 

This function decrypts the encrypted string crypt_str using pass_str as the pass- 
word. crypt_str should be a string returned from encode( ). 

DECODE (crypt_str , pass_str) 
RETURNS: string 

MD5 

This function calculates an MD5 checksum for the string. The value is returned as 
a32-character alpha-numeric string. This is the same as the md5() functions used 
by PHP. 

MD5(string) 
RETURNS: string 

LASTJNSERTJD 

This function returns the last automatically generated value that was inserted into 
an auto_increment column. 

LAST_INSERT_ID() 
RETURNS: int 

GET_LOCK 

This function tries to obtain a lock with a name given by the string str, with a 
timeout of timeout seconds. Returns 1 if the lock was obtained successfully, if 
the attempt timed out, or null if an error occurred (such as running out of memory 
or the thread being killed with mysqi admin kill). A lock is released release_ 
locko is executed, a new GET_L0CK()a new GET_LOCK() is executed, or the thread 
terminates. 

GET_LOCK(str, timeout) 
RETURNS: int 



Appendix I: MySQL Function Reference 583 



RELEASE LOCK 



This function releases the lock named by the string str that was obtained with 
get_lock( ). It returns 1 if the lock was released, if the lock wasn't locked by this 
thread (in which case the lock is not released) and null if the named lock didn't 
exist. 

RELEASE_LOCK(str) 
RETURNS: int 



Functions for Use with GROUP 
BY Clauses 

Most of the functions that are used with the GROUP BY clase were covered in 
Chapter 3. There are two additional functions which we did not cover there. 

STD/STDDEV 

This function returns the standard deviation of expr. It is an extension of ANSI 
SQL. The stddev( ) form of this function is provided for Oracle compatibility. 

STDtexpr ) 

or 

STDDEV(expr) 
RETURNS: Tloat 

BIT_OR 

This function returns the bitwise OR of all bits in expr. The calculation is performed 
with 64- bit (bigint) precision. 

BIT_OR(expr) 
RETURNS: int 

BIT_AND 

This function returns the bitwise and of all bits in expr. The calculation is performed 
with 64- bit (bigint) precision. 

BIT_AND(expr) 
RETURNS: int 



Appendix J 

What's on the CD- ROM 



To get the applications Sections III and IV working you first need to install 
Apache, PHP, and MySQL. You can find these applications on this book's CD-ROM 
in the /apache, /php, and /mysql directories. Each has subdirectories for Windows 
and Unix. Use whichever is appropriate. Then follow the instructions in Appendix 
B to install these applications. 

Once Apache, PHP, and MySQL are installed, you will need to copy the PHP 
scripts that load the databases and run the applications. Copy the entire /book 
directory from the CD, with all of its subfolders, to the htdocs/ directory of your 
Apache installation. 

The files that install the databases are kept in the book/install/ directory. If 
Apache is running on your system, all you will need to do to install the databases 
is open the correct URL in your browser: http://yourdomain/book/instal l / 
index. php. (If Apache is on your local machine, the acutal URL will likely be 

http://localhost/book/install/index.php). 

Follow the instructions to install the databases you wish to use. Note that for the 
install script, you will need to enter a valid hostname, username, and password in 
the mysql_connect() function on the fourth line of the install/index. php file. You 
can then access all of the applications by moving to http://yourdomain/book/ 

i ndex. html / 

You should then open the book/functions/db.php file. In the first function, 
change the $user, $password, and $server arguments to reflect strings that are 
valid for your MySQL installation. 

Also on the CD, you will find the following: 

♦ A PDF version of this book. 

♦ Adobe Acrobat Reader 4.0. 

♦ PHPmyadmin— This excellent utility gives MySQL a graphical user inter- 
face via a Web browser using PHP scripts. 

♦ Manual Lemos' Form Creation and Validation Class— This is a very com- 
plete set of scripts for creating forms and validating form input. 

♦ Manual Lemos' Database Abstraction Layer— This can be helpful if you 
need to access more than one database from your PHP scripts. 



585 



586 Part V: Appendixes 



♦ PHP Base Library —A set of scripts useful for user authentication and 
other common processes. 

♦ Scripts from Appendix G. 

♦ PBM Plus— This utility for manipulating images is used in Chapter 10. 
It will only work on Unix systems. 

All files with .php extensions can simply be copied to the Web server directory 
and will execute when the page is accessed. 

Files with .tar.gz extensions are intended for Unix systems and must be uncom- 
pressed before you will be able to use them. Use the following commands: 

gunzip f i 1 ename . tar .gz 
tar xf filename. tar 

All files with .zip extensions must be uncompressed using a Windows zip utility 
such as WinZip (available at http://www.winzip.com). 

Once you've uncompressed the packages, see the README or INSTALL files for 
installation instructions. 



IDG Books Worldwide, Inc. 
End- User License Agreement 

READ THIS. You should carefully read these terms and conditions before opening 
the software packet(s) included with this book ("Book"). This is a license agreement 
("Agreement") between you and IDG Books Worldwide, Inc. ("IDGB"). By opening 
the accompanying software packet(s), you acknowledge that you have read and 
accept the following terms and conditions. If you do not agree and do not want to 
be bound by such terms and conditions, promptly return the Book and the unopened 
software packet(s) to the place you obtained them for a full refund. 

1. License Grant. IDGB grants to you (either an individual or entity) a 
nonexclusive license to use one copy of the enclosed software program(s) 
(collectively, the "Software") solely for your own personal or business pur- 
poses on a single computer (whether a standard computer or a worksta- 
tion component of a multiuser network). The Software is in use on a 
computer when it is loaded into temporary memory (RAM) or installed 
into permanent memory (hard disk, CD-ROM, or other storage device). 
IDGB reserves all rights not expressly granted herein. 

2. Ownership. IDGB is the owner of all right, title, and interest, including 
copyright, in and to the compilation of the Software recorded on the 
disk(s) or CD-ROM ("Software Media"). Copyright to the individual pro- 
grams recorded on the Software M edia is owned by the author or other 
authorized copyright owner of each program. Ownership of the Software 
and all proprietary rights relating thereto remain with IDGB and its 
licensers. 

3. Restrictions On Use and Transfer. 

(a) You may only (i) make one copy of the Software for backup or archival 
purposes, or (ii) transfer the Software to a single hard disk, provided 
that you keep the original for backup or archival purposes. You may 
not (i) rent or lease the Software, (ii) copy or reproduce the Software 
through a LAN or other network system or through any computer sub- 
scriber system or bulletin-board system, or (iii) modify, adapt, or create 
derivative works based on the Software. 

(b) You may not reverse engineer, decompile, or disassemble the Software. 
You may transfer the Software and user documentation on a perma- 
nent basis, provided that the transferee agrees to accept the terms and 
conditions of this Agreement and you retain no copies. If the Software 
is an update or has been updated, any transfer must include the most 
recent update and all prior versions. 



4. Restrictions on Use of Individual Programs. You must follow the individ- 
ual requirements and restrictions detailed for each individual program in 
Appendix J of this Book. These limitations are also contained in the indi- 
vidual license agreements recorded on the Software Media. These limita- 
tions may include a requirement that after using the program for a specified 
period of time, the user must pay a registration fee or discontinue use. By 
opening the Software packet(s), you will be agreeing to abide by the licenses 
and restrictions for these individual programs that are detailed in Appendix 

J and on the Software M edia. None of the material on this Software M edia 
or listed in this Book may ever be redistributed, in original or modified 
form, for commercial purposes. 

5. Limited Warranty. 

(a) IDGB warrants that the Software and Software M edia are free from 
defects in materials and workmanship under normal use for a period of 
sixty (60) days from the date of purchase of this Book. If IDGB receives 
notification within the warranty period of defects in materials or work- 
manship, IDGB will replace the defective Software Media. 

(b) IDGB AND THE AUTHORS OF THE BOOK DISCLAIM ALL OTHER 
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITA- 
TION IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
FOR A PARTICULAR PURPOSE, WITH RESPECT TO THE SOFTWARE, 
THE PROGRAMS, THE SOURCE CODE CONTAINED THEREIN, AND/OR 
THE TECHNIQUES DESCRIBED IN THIS BOOK. IDGB DOES NOT WAR- 
RANT THAT THE FUNCTIONS CONTAINED IN THE SOFTWARE WILL 
MEET YOUR REQUIREMENTS OR THAT THE OPERATION OF THE 
SOFTWARE WILL BE ERROR FREE. 

(c) This limited warranty gives you specific legal rights, and you may have 
other rights that vary from jurisdiction to jurisdiction. 

6. Remedies. 

(a) IDGB's entire liability and your exclusive remedy for defects in materi- 
als and workmanship shall be limited to replacement of the Software 
Media, which may be returned to IDGB with a copy of your receipt at 
the following address: Software Media Fulfillment Department, Attn.: 
MySQL/PHP Database Applications, IDG Books Worldwide, Inc., 10475 
Crosspoint Blvd., Indianapolis, IN 46256, or call 1-800-762-2974. 
Please allow three to four weeks for delivery. This Limited Warranty is 
void if failure of the Software Media has resulted from accident, abuse, 
or misapplication. Any replacement Software Media will be warranted 
for the remainder of the original warranty period or thirty (30) days, 
whichever is longer. 



(b) In no event shall IDGB or the authors be liable for any damages what- 
soever (including without limitation damages for loss of business prof- 
its, business interruption, loss of business information, or any other 
pecuniary loss) arising from the use of or inability to use the Book or 
the Software, even if IDGB has been advised of the possibility of such 
damages. 

(c) Because some jurisdictions do not allow the exclusion or limitation of 
liability for consequential or incidental damages, the above limitation 
or exclusion may not apply to you. 

7. U.S. Government Restricted Rights. Use, duplication, or disclosure of the 
Software by the U.S. Government is subject to restrictions stated in para- 
graph (c)(l)(ii) of the Rights in Technical Data and Computer Software 
clause of DFARS 252.227-7013, and in subparagraphs (a) through (d) of 
the Commercial Computer— Restricted Rights clause at FAR 52.227-19, 
and in similar clauses in the NASA FAR supplement, when applicable. 

8. General. This Agreement constitutes the entire understanding of the par- 
ties and revokes and supersedes all prior agreements, oral or written, 
between them and may not be modified or amended except in a writing 
signed by both parties hereto that specifically refers to this Agreement. 
This Agreement shall take precedence over any other documents that may 
be in conflict herewith. If any one or more provisions contained in this 
Agreement are held by any court or tribunal to be invalid, illegal, or oth- 
erwise unenforceable, each and every other provision shall remain in full 
force and effect. 



GNU GENERAL PUBLIC LICENSE 

Everyone is permitted to copy and distribute verbatim copies of this license docu- 
ment, but changing it is not allowed. 

Preamble 

The licenses for most software are designed to take away your freedom to share and 
change it. By contrast, the GNU General Public License is intended to guarantee 
your freedom to share and change free software— to make sure the software is free 
for all its users. This General Public License applies to most of the Free Software 
Foundation's software and to any other program whose authors commit to using it. 
(Some other Free Software Foundation software is covered by the GNU Library 
General Public License instead.) You can apply it to your programs, too. 

When we speak of free software, we are referring to freedom, not price. Our 
General Public Licenses are designed to make sure that you have the freedom to 
distribute copies of free software (and charge for this service if you wish), that you 
receive source code or can get it if you want it, that you can change the software or 
use pieces of it in new free programs; and that you know you can do these things. 

To protect your rights, we need to make restrictions that forbid anyone to deny 
you these rights or to ask you to surrender the rights. These restrictions translate to 
certain responsibilities for you if you distribute copies of the software, or if you 
modify it. 

For example, if you distribute copies of such a program, whether gratis or for a 
fee, you must give the recipients all the rights that you have. You must make sure 
that they, too, receive or can get the source code. And you must show them these 
terms so they know their rights. 

We protect your rights with two steps: (1) copyright the software, and (2) offer 
you this license which gives you legal permission to copy, distribute and/or modify 
the software. 

Also, for each author's protection and ours, we want to make certain that every- 
one understands that there is no warranty for this free software. If the software is 
modified by someone else and passed on, we want its recipients to know that what 
they have is not the original, so that any problems introduced by others will not 
reflect on the original authors' reputations. 

Finally, any free program is threatened constantly by software patents. We wish 
to avoid the danger that redistributors of a free program will individually obtain 
patent licenses, in effect making the program proprietary. To prevent this, we have 
made it clear that any patent must be licensed for everyone's free use or not licensed 
at all. 

The precise terms and conditions for copying, distribution and modification 
follow. 



TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION, 
AND MODIFICATION 

0. This License applies to any program or other work which contains a 
notice placed by the copyright holder saying it may be distributed under 
the terms of this General Public License. The "Program", below, refers to 
any such program or work, and a "work based on the Program" means 
either the Program or any derivative work under copyright law: that is to 
say, a work containing the Program or a portion of it, either verbatim or 
with modifications and/or translated into another language. (Hereinafter, 
translation is included without limitation in the term "modification") Each 
licensee is addressed as "you". 

Activities other than copying, distribution and modification are not cov- 
ered by this License; they are outside its scope. The act of running the 
Program is not restricted, and the output from the Program is covered 
only if its contents constitute a work based on the Program (independent 
of having been made by running the Program). Whether that is true 
depends on what the Program does. 

1. You may copy and distribute verbatim copies of the Program's source 
code as you receive it, in any medium, provided that you conspicuously 
and appropriately publish on each copy an appropriate copyright notice 
and disclaimer of warranty; keep intact all the notices that refer to this 
License and to the absence of any warranty; and give any other recipients 
of the Program a copy of this License along with the Program. 

You may charge a fee for the physical act of transferring a copy, and you 
may at your option offer warranty protection in exchange for a fee. 

2. You may modify your copy or copies of the Program or any portion of it, 
thus forming a work based on the Program, and copy and distribute such 
modifications or work under the terms of Section 1 above, provided that 
you also meet all of these conditions: 

a) You must cause the modified files to carry prominent notices stating 
that you changed the files and the date of any change. 

b) You must cause any work that you distribute or publish, that in whole 
or in part contains or is derived from the Program or any part thereof, 
to be licensed as a whole at no charge to all third parties under the 
terms of this License. 



c) If the modified program normally reads commands interactively when 
run, you must cause it, when started running for such interactive use in 
the most ordinary way, to print or display an announcement including 
an appropriate copyright notice and a notice that there is no warranty 
(or else, saying that you provide a warranty) and that users may redis- 
tribute the program under these conditions, and telling the user how to 
view a copy of this License. (Exception: if the Program itself is interac- 
tive but does not normally print such an announcement, your work 
based on the Program is not required to print an announcement.) 

These requirements apply to the modified work as a whole. If identifiable 
sections of that work are not derived from the Program, and can be rea- 
sonably considered independent and separate works in themselves, then 
this License, and its terms, do not apply to those sections when you dis- 
tribute them as separate works. But when you distribute the same sections 
as part of a whole which is a work based on the Program, the distribution 
of the whole must be on the terms of this License, whose permissions for 
other licensees extend to the entire whole, and thus to each and every part 
regardless of who wrote it. 

Thus, it is not the intent of this section to claim rights or contest your 
rights to work written entirely by you; rather, the intent is to exercise the 
right to control the distribution of derivative or collective works based on 
the Program. 

In addition, mere aggregation of another work not based on the Program 
with the Program (or with a work based on the Program) on a volume of a 
storage or distribution medium does not bring the other work under the 
scope of this License. 

3. You may copy and distribute the Program (or a work based on it, under 
Section 2) in object code or executable form under the terms of Sections 1 
and 2 above provided that you also do one of the following: 

a) Accompany it with the complete corresponding machine- readable source 
code, which must be distributed under the terms of Sections 1 and 2 
above on a medium customarily used for software interchange; or, 

b) Accompany it with a written offer, valid for at least three years, to 
give any third party, for a charge no more than your cost of physically 
performing source distribution, a complete machine-readable copy of 
the corresponding source code, to be distributed under the terms of 
Sections 1 and 2 above on a medium customarily used for software 
interchange; or, 



c) Accompany it with the information you received as to the offer to dis- 
tribute corresponding source code. (This alternative is allowed only for 
noncommercial distribution and only if you received the program in 
object code or executable form with such an offer, in accord with 
Subsection b above.) 

The source code for a work means the preferred form of the work for 
making modifications to it. For an executable work, complete source code 
means all the source code for all modules it contains, plus any associated 
interface definition files, plus the scripts used to control compilation and 
installation of the executable. However, as a special exception, the source 
code distributed need not include anything that is normally distributed (in 
either source or binary form) with the major components (compiler, ker- 
nel, and so on) of the operating system on which the executable runs, 
unless that component itself accompanies the executable. 

If distribution of executable or object code is made by offering access to 
copy from a designated place, then offering equivalent access to copy the 
source code from the same place counts as distribution of the source code, 
even though third parties are not compelled to copy the source along with 
the object code. 

4. You may not copy, modify, sublicense, or distribute the Program except 
as expressly provided under this License. Any attempt otherwise to copy, 
modify, sublicense or distribute the Program is void, and will automati- 
cally terminate your rights under this License. However, parties who have 
received copies, or rights, from you under this License will not have their 
licenses terminated so long as such parties remain in full compliance. 

5. You are not required to accept this License, since you have not signed it. 
However, nothing else grants you permission to modify or distribute the 
Program or its derivative works. These actions are prohibited by law if 
you do not accept this License. Therefore, by modifying or distributing the 
Program (or any work based on the Program), you indicate your accep- 
tance of this License to do so, and all its terms and conditions for copy- 
ing, distributing or modifying the Program or works based on it. 

6. Each time you redistribute the Program (or any work based on the 
Program), the recipient automatically receives a license from the original 
licensor to copy, distribute or modify the Program subject to these terms 
and conditions. You may not impose any further restrictions on the recipi- 
ents' exercise of the rights granted herein. You are not responsible for 
enforcing compliance by third parties to this License. 

7. If, as a consequence of a court judgment or allegation of patent infringe- 
ment or for any other reason (not limited to patent issues), conditions are 
imposed on you (whether by court order, agreement or otherwise) that 
contradict the conditions of this License, they do not excuse you from the 



conditions of this License. If you cannot distribute so as to satisfy simul- 
taneously your obligations under this License and any other pertinent 
obligations, then as a consequence you may not distribute the Program at 
all. For example, if a patent license would not permit royalty-free redistri- 
bution of the Program by all those who receive copies directly or indi- 
rectly through you, then the only way you could satisfy both it and this 
License would be to refrain entirely from distribution of the Program. 

If any portion of this section is held invalid or unenforceable under any 
particular circumstance, the balance of the section is intended to apply 
and the section as a whole is intended to apply in other circumstances. 

It is not the purpose of this section to induce you to infringe any patents 
or other property right claims or to contest validity of any such claims; 
this section has the sole purpose of protecting the integrity of the free 
software distribution system, which is implemented by public license 
practices. Many people have made generous contributions to the wide 
range of software distributed through that system in reliance on consis- 
tent application of that system; it is up to the author/donor to decide if 
he or she is willing to distribute software through any other system and 
a licensee cannot impose that choice. 

This section is intended to make thoroughly clear what is believed to be a 
consequence of the rest of this License. 

8. If the distribution and/or use of the Program is restricted in certain coun- 
tries either by patents or by copyrighted interfaces, the original copyright 
holder who places the Program under this License may add an explicit 
geographical distribution limitation excluding those countries, so that dis- 
tribution is permitted only in or among countries not thus excluded. In 
such case, this License incorporates the limitation as if written in the body 
of this License. 

9. The Free Software Foundation may publish revised and/or new versions of 
the General Public License from time to time. Such new versions will be 
similar in spirit to the present version, but may differ in detail to address 
new problems or concerns. 

Each version is given a distinguishing version number. If the Program 
specifies a version number of this License which applies to it and "any 
later version", you have the option of following the terms and conditions 
either of that version or of any later version published by the Free Soft- 
ware Foundation. If the Program does not specify a version number of 
this License, you may choose any version ever published by the Free 
Software Foundation. 

10. If you wish to incorporate parts of the Program into other free programs 
whose distribution conditions are different, write to the author to ask for 



permission. For software which is copyrighted by the Free Software 
Foundation, write to the Free Software Foundation; we sometimes make 
exceptions for this. Our decision will be guided by the two goals of pre- 
serving the free status of all derivatives of our free software and of pro- 
moting the sharing and reuse of software generally. 

NO WARRANTY 

11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS 
NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING 
THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE 
PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER 
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND 
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PRO- 
GRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY 
SERVICING, REPAIR OR CORRECTION. 

12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO 
IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY 
WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMIT- 
TED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GEN- 
ERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING 
BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INAC- 
CURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAIL- 
URE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGES. 

END OF TERMS AND CONDITIONS